Merge branch 'master' into puzzle-ui

* master:
  also name single async caches and mixed cache
  name async caches and expire the result future
  remove user ranking on account closure
  {master} fix game tournament leaderboard style
This commit is contained in:
Thibault Duplessis 2016-11-28 13:17:28 +01:00
commit 3a42852b49
42 changed files with 213 additions and 125 deletions

View file

@ -45,6 +45,11 @@ final class Env(
getPlayTime = Env.game.playTime.apply,
completionRate = Env.playban.api.completionRate) _
lazy val teamInfo = new mashup.TeamInfoApi(
api = Env.team.api,
getForumNbPosts = Env.forum.categApi.teamNbPosts _,
getForumPosts = Env.forum.recent.team _)
private def tryDailyPuzzle(): Fu[Option[lila.puzzle.DailyPuzzle]] =
scala.concurrent.Future {
Env.puzzle.daily()

View file

@ -66,6 +66,7 @@ object Lobby extends LilaController {
headers: Headers)
private val cache = lila.memo.AsyncCache[RequestKey, Html](
name = "lobby.homeCache",
f = renderRequestKey,
timeToLive = 1 second)

View file

@ -130,7 +130,9 @@ object Mod extends LilaController {
}
private[controllers] val ipIntelCache =
lila.memo.AsyncCache[String, Int](ip => {
lila.memo.AsyncCache[String, Int](
name = "ipIntel",
f = ip => {
import play.api.libs.ws.WS
import play.api.Play.current
val email = Env.api.Net.Email

View file

@ -15,6 +15,7 @@ object Monitor extends LilaController {
val coachPageView = "servers.lichess.statsite.counts.main.counter.coach.page_view.profile"
}
private val coachPageViewCache = lila.memo.AsyncCache[lila.user.User.ID, Result](
name = "monitor.coachPageView",
f = userId =>
Env.coach.api byId lila.coach.Coach.Id(userId) flatMap {
_ ?? { coach =>

View file

@ -18,6 +18,7 @@ object Prismic {
}
private val fetchPrismicApi = AsyncCache.single[PrismicApi](
name = "prismic.fetchPrismicApi",
f = PrismicApi.get(Env.api.PrismicApiUrl, logger = prismicLogger),
timeToLive = 1 minute)

View file

@ -15,10 +15,7 @@ object Team extends LilaController {
private def forms = Env.team.forms
private def api = Env.team.api
private def paginator = Env.team.paginator
private lazy val teamInfo = mashup.TeamInfo(
api = api,
getForumNbPosts = Env.forum.categApi.teamNbPosts _,
getForumPosts = Env.forum.recent.team _) _
private lazy val teamInfo = Env.current.teamInfo
def all(page: Int) = Open { implicit ctx =>
NotForKids {

View file

@ -22,22 +22,25 @@ case class TeamInfo(
def hasRequests = requests.nonEmpty
}
object TeamInfo {
final class TeamInfoApi(
api: TeamApi,
getForumNbPosts: String => Fu[Int],
getForumPosts: String => Fu[List[MiniForumPost]]) {
private case class Cachable(bestUserIds: List[User.ID], toints: Int)
private def fetchCachable(id: String): Fu[Cachable] = for {
userIds (MemberRepo userIdsByTeam id).thenPp(s"team $id userIds")
bestUserIds UserRepo.idsByIdsSortRating(userIds, 10).thenPp(s"team $id bestUserIds")
toints UserRepo.idsSumToints(userIds).thenPp(s"team $id toints")
} yield Cachable(bestUserIds, toints)
private val cache = lila.memo.AsyncCache[String, Cachable](
teamId => for {
userIds MemberRepo userIdsByTeam teamId
bestUserIds UserRepo.idsByIdsSortRating(userIds, 10)
toints UserRepo.idsSumToints(userIds)
} yield Cachable(bestUserIds, toints),
name = "teamInfo",
f = fetchCachable,
timeToLive = 10 minutes)
def apply(
api: TeamApi,
getForumNbPosts: String => Fu[Int],
getForumPosts: String => Fu[List[MiniForumPost]])(team: Team, me: Option[User]): Fu[TeamInfo] = for {
def apply(team: Team, me: Option[User]): Fu[TeamInfo] = for {
requests (team.enabled && me.??(m => team.isCreator(m.id))) ?? api.requestsWithUsers(team)
mine = me.??(m => api.belongsTo(team.id, m.id))
requestedByMe !mine ?? me.??(m => RequestRepo.exists(team.id, m.id))

View file

@ -10,7 +10,7 @@ net {
ip = "5.196.91.160"
asset {
domain = ${net.domain}
version = 1245
version = 1246
}
email = "contact@lichess.org"
crawlable = false

View file

@ -56,6 +56,7 @@ final class Env(
import lila.db.dsl._
private val coll = db("flag")
private val cache = lila.memo.MixedCache.single[Int](
name = "asset.version",
f = coll.primitiveOne[BSONNumberLike]($id("asset"), "version").map {
_.fold(Net.AssetVersion)(_.toInt max Net.AssetVersion)
},

View file

@ -39,6 +39,7 @@ final class BlogApi(prismicUrl: String, collection: String) {
}
private val fetchPrismicApi = AsyncCache.single[Api](
name = "blogApi.fetchPrismicApi",
f = Api.get(prismicUrl, cache = cache, logger = prismicLogger),
timeToLive = 10 seconds)

View file

@ -5,7 +5,8 @@ import scala.concurrent.duration._
final class LastPostCache(api: BlogApi, ttl: Duration, collection: String) {
private val cache = lila.memo.MixedCache.single[List[MiniPost]](
api.prismicApi flatMap { prismic =>
name = "blog.lastPost",
f = api.prismicApi flatMap { prismic =>
api.recent(prismic, none, 3) map {
_ ?? {
_.results.toList flatMap MiniPost.fromDocument(collection)

View file

@ -37,7 +37,10 @@ final class ChallengeApi(
def byId = repo byId _
val countInFor = AsyncCache(repo.countCreatedByDestId, maxCapacity = 20000)
val countInFor = AsyncCache(
name = "challenge.countInFor",
f = repo.countCreatedByDestId,
maxCapacity = 20000)
def createdByChallengerId = repo createdByChallengerId _

View file

@ -18,6 +18,7 @@ final class CoachApi(
import BsonHandlers._
private val cache = AsyncCache.single[List[Coach]](
name = "coach.list",
f = coachColl.find($empty).list[Coach](),
timeToLive = 10 minutes)

View file

@ -47,4 +47,9 @@ object Future {
def delay[A](duration: FiniteDuration)(run: => Fu[A])(implicit system: akka.actor.ActorSystem): Fu[A] =
akka.pattern.after(duration, system.scheduler)(run)
def neverCompletes[T]: Fu[T] = {
val p = scala.concurrent.Promise[T]()
p.future
}
}

View file

@ -1,8 +1,8 @@
package lila
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.Future
import scala.concurrent.duration._
import scala.concurrent.Future
object PimpedFuture {
@ -98,12 +98,14 @@ object PimpedFuture {
}
def withTimeout(duration: FiniteDuration, error: => Throwable)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(fua,
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future failed error))
}
def withTimeoutDefault(duration: FiniteDuration, default: => A)(implicit system: akka.actor.ActorSystem): Fu[A] = {
Future firstCompletedOf Seq(fua,
Future firstCompletedOf Seq(
fua,
akka.pattern.after(duration, system.scheduler)(Future(default)))
}

View file

@ -10,7 +10,9 @@ final class EventApi(coll: Coll) {
import BsonHandlers._
val promotable = AsyncCache.single(fetchPromotable, timeToLive = 5 minutes)
val promotable = AsyncCache.single(
name = "event.promotable",
fetchPromotable, timeToLive = 5 minutes)
def fetchPromotable: Fu[List[Event]] = coll.find($doc(
"enabled" -> true,

View file

@ -15,6 +15,7 @@ private final class FishnetRepo(
import BSONHandlers._
private val clientCache = AsyncCache[Client.Key, Option[Client]](
name = "fishnet.client",
f = key => clientColl.find(selectClient(key)).uno[Client],
timeToLive = 10 seconds)

View file

@ -36,6 +36,7 @@ final class Cached(
// timeToLive = 1 hour)
private val countShortTtl = AsyncCache[Bdoc, Int](
name = "game.countShortTtl",
f = (o: Bdoc) => coll countSel o,
timeToLive = 5.seconds)

View file

@ -65,6 +65,7 @@ final class CrosstableApi(
// to avoid creating it twice during a new matchup
private val creationCache = AsyncCache[(String, String), Option[Crosstable]](
name = "crosstable",
f = (create _).tupled,
timeToLive = 20 seconds)

View file

@ -16,7 +16,9 @@ private[i18n] final class Context(gitUrl: String, gitFile: String, keys: I18nKey
def get: Fu[Contexts] = cache(true)
private val cache = AsyncCache.single[Contexts](fetch, timeToLive = 1 hour)
private val cache = AsyncCache.single[Contexts](
name = "i18n.contexts",
fetch, timeToLive = 1 hour)
private def parse(text: String): Contexts =
text.lines.toList.map(_.trim).filter(_.nonEmpty).map(_.split('=')).foldLeft(Map[String, String]()) {

View file

@ -26,6 +26,7 @@ final class SeekApi(
.cursor[Seek]()
private val cache = AsyncCache[CacheKey, List[Seek]](
name = "lobby.seek.list",
f = {
case ForAnon => allCursor.gather[List](maxPerPage)
case ForUser => allCursor.gather[List]()

View file

@ -17,18 +17,29 @@ final class AsyncCache[K, V] private (cache: Cache[V], f: K => Fu[V]) {
object AsyncCache {
implicit private val system = lila.common.PlayApp.system
def apply[K, V](
name: String,
f: K => Fu[V],
maxCapacity: Int = 500,
initialCapacity: Int = 16,
timeToLive: Duration = Duration.Inf,
timeToIdle: Duration = Duration.Inf) = new AsyncCache(
timeToIdle: Duration = Duration.Inf,
resultTimeout: FiniteDuration = 5 seconds) = new AsyncCache(
cache = LruCache(maxCapacity, initialCapacity, timeToLive, timeToIdle),
f = f)
f = (key: K) => f(key).withTimeout(
duration = resultTimeout,
error = lila.common.LilaException(s"AsyncCache $name $key timed out after $resultTimeout"))
)
def single[V](
name: String,
f: => Fu[V],
timeToLive: Duration = Duration.Inf) = new AsyncCache[Boolean, V](
timeToLive: Duration = Duration.Inf,
resultTimeout: FiniteDuration = 5 seconds) = new AsyncCache[Boolean, V](
cache = LruCache(timeToLive = timeToLive),
f = _ => f)
f = _ => f.withTimeout(
duration = resultTimeout,
error = lila.common.LilaException(s"AsyncCache $name single timed out after $resultTimeout")))
}

View file

@ -30,37 +30,39 @@ object MixedCache {
async.remove(k) >>- sync.invalidate(k)
def apply[K, V](
name: String,
f: K => Fu[V],
timeToLive: Duration = Duration.Inf,
awaitTime: FiniteDuration = 10.milliseconds,
default: K => V,
logger: lila.log.Logger): MixedCache[K, V] = {
val async = AsyncCache(f, maxCapacity = 10000, timeToLive = 1 minute)
val async = AsyncCache(name, f, maxCapacity = 10000, timeToLive = 1 minute)
val sync = Builder.cache[K, V](
timeToLive,
(k: K) => async(k) await makeTimeout(awaitTime))
new MixedCache(sync, default, invalidate(async, sync) _, logger branch "MixedCache")
}
def fromAsync[K, V](
async: AsyncCache[K,V],
timeToLive: Duration = Duration.Inf,
awaitTime: FiniteDuration = 10.milliseconds,
default: K => V,
logger: lila.log.Logger): MixedCache[K, V] = {
val sync = Builder.cache[K, V](
timeToLive,
(k: K) => async(k) await makeTimeout(awaitTime))
new MixedCache(sync, default, invalidate(async, sync) _, logger branch "MixedCache")
}
// def fromAsync[K, V](
// async: AsyncCache[K,V],
// timeToLive: Duration = Duration.Inf,
// awaitTime: FiniteDuration = 10.milliseconds,
// default: K => V,
// logger: lila.log.Logger): MixedCache[K, V] = {
// val sync = Builder.cache[K, V](
// timeToLive,
// (k: K) => async(k) await makeTimeout(awaitTime))
// new MixedCache(sync, default, invalidate(async, sync) _, logger branch "MixedCache")
// }
def single[V](
name: String,
f: => Fu[V],
timeToLive: Duration = Duration.Inf,
awaitTime: FiniteDuration = 5.milliseconds,
default: V,
logger: lila.log.Logger): MixedCache[Boolean, V] = {
val async = AsyncCache.single(f, timeToLive = 1 minute)
val async = AsyncCache.single(name, f, timeToLive = 1 minute)
val sync = Builder.cache[Boolean, V](
timeToLive,
(_: Boolean) => async(true) await makeTimeout(awaitTime))

View file

@ -56,6 +56,7 @@ final class Gamify(
def leaderboards = leaderboardsCache(true)
private val leaderboardsCache = AsyncCache.single[Leaderboards](
name = "mod.leaderboards",
f = mixedLeaderboard(DateTime.now minusDays 1, none) zip
mixedLeaderboard(DateTime.now minusWeeks 1, none) zip
mixedLeaderboard(DateTime.now minusMonths 1, none) map {

View file

@ -20,10 +20,10 @@ final class NotifyApi(
def getNotifications(userId: Notification.Notifies, page: Int): Fu[Paginator[Notification]] = Paginator(
adapter = new Adapter(
collection = repo.coll,
selector = repo.userNotificationsQuery(userId),
projection = $empty,
sort = repo.recentSort),
collection = repo.coll,
selector = repo.userNotificationsQuery(userId),
projection = $empty,
sort = repo.recentSort),
currentPage = page,
maxPerPage = perPage)
@ -34,7 +34,10 @@ final class NotifyApi(
repo.markAllRead(userId) >> unreadCountCache.remove(userId)
private val unreadCountCache =
AsyncCache(repo.unreadNotificationsCount, maxCapacity = 20000)
AsyncCache(
name = "notify.unreadCountCache",
f = repo.unreadNotificationsCount,
maxCapacity = 20000)
def unreadCount(userId: Notification.Notifies): Fu[Notification.UnreadCount] =
unreadCountCache(userId) map Notification.UnreadCount.apply

View file

@ -219,6 +219,7 @@ final class PlanApi(
}
private val recentChargeUserIdsCache = AsyncCache[Int, List[User.ID]](
name = "plan.recentChargeUserIds",
f = nb => chargeColl.primitive[User.ID](
$empty, sort = $doc("date" -> -1), nb = nb * 3 / 2, "userId"
) flatMap filterUserIds map (_ take nb),
@ -230,6 +231,7 @@ final class PlanApi(
chargeColl.find($doc("userId" -> user.id)).sort($doc("date" -> -1)).list[Charge]()
private val topPatronUserIdsCache = AsyncCache[Int, List[User.ID]](
name = "plan.topPatronUserIds",
f = nb => chargeColl.aggregate(
Match($doc("userId" $exists true)), List(
GroupField("userId")("total" -> SumField("cents")),

View file

@ -15,7 +15,10 @@ final class PrefApi(
cacheTtl: Duration) {
private def fetchPref(id: String): Fu[Option[Pref]] = coll.find($id(id)).uno[Pref]
private val cache = AsyncCache(fetchPref, timeToLive = cacheTtl)
private val cache = AsyncCache(
name = "pref.fetchPref",
f = fetchPref,
timeToLive = cacheTtl)
private implicit val prefBSONHandler = new BSON[Pref] {

View file

@ -14,7 +14,10 @@ private[puzzle] final class Daily(
scheduler: Scheduler) {
private val cache =
lila.memo.AsyncCache.single[Option[DailyPuzzle]](f = find, timeToLive = 10 minutes)
lila.memo.AsyncCache.single[Option[DailyPuzzle]](
name = "puzzle.daily",
f = find,
timeToLive = 10 minutes)
def apply(): Fu[Option[DailyPuzzle]] = cache apply true

View file

@ -33,7 +33,9 @@ private[puzzle] final class PuzzleApi(
private def lastId: Fu[Int] = lila.db.Util findNextId puzzleColl map (_ - 1)
val cachedLastId = lila.memo.AsyncCache.single(lastId, timeToLive = 5 minutes)
val cachedLastId = lila.memo.AsyncCache.single(
name = "puzzle.lastId",
lastId, timeToLive = 5 minutes)
def importOne(json: JsValue, token: String): Fu[PuzzleId] =
if (token != apiToken) fufail("Invalid API token")

View file

@ -57,12 +57,14 @@ final class RelationApi(
fetchFollows(u1, u2) flatMap { _ ?? fetchFollows(u2, u1) }
private val countFollowingCache = AsyncCache[ID, Int](
name = "relation.count.following",
f = userId => coll.countSel($doc("u1" -> userId, "r" -> Follow)),
timeToLive = 10 minutes)
def countFollowing(userId: ID) = countFollowingCache(userId)
private val countFollowersCache = AsyncCache[ID, Int](
name = "relation.count.followers",
f = userId => coll.countSel($doc("u2" -> userId, "r" -> Follow)),
timeToLive = 10 minutes)

View file

@ -5,6 +5,7 @@ import scala.concurrent.duration._
private[simul] final class Cached(repo: SimulRepo) {
private val nameCache = lila.memo.MixedCache[Simul.ID, Option[String]](
name = "simul.name",
((id: Simul.ID) => repo find id map2 { (simul: Simul) => simul.fullName }),
timeToLive = 6 hours,
default = _ => none,

View file

@ -90,9 +90,13 @@ final class Env(
def isHosting(userId: String): Fu[Boolean] = api.currentHostIds map (_ contains userId)
val allCreated = lila.memo.AsyncCache.single(repo.allCreated, timeToLive = CreatedCacheTtl)
val allCreated = lila.memo.AsyncCache.single(
name = "simul.allCreated",
repo.allCreated, timeToLive = CreatedCacheTtl)
val allCreatedFeaturable = lila.memo.AsyncCache.single(repo.allCreatedFeaturable, timeToLive = CreatedCacheTtl)
val allCreatedFeaturable = lila.memo.AsyncCache.single(
name = "simul.allCreatedFeaturable",
repo.allCreatedFeaturable, timeToLive = CreatedCacheTtl)
def version(tourId: String): Fu[Int] =
socketHub ? Ask(tourId, GetVersion) mapTo manifest[Int]

View file

@ -33,6 +33,7 @@ private[simul] final class SimulApi(
def currentHostIds: Fu[Set[String]] = currentHostIdsCache apply true
private val currentHostIdsCache = AsyncCache.single[Set[String]](
name = "simul.currentHostIds",
f = repo.allStarted map (_ map (_.hostId) toSet),
timeToLive = 10 minutes)

View file

@ -5,7 +5,9 @@ import scala.concurrent.duration._
private[team] final class Cached {
private val nameCache = MixedCache[String, Option[String]](TeamRepo.name,
private val nameCache = MixedCache[String, Option[String]](
name = "team.name",
TeamRepo.name,
timeToLive = 6 hours,
default = _ => none,
logger = logger)
@ -13,6 +15,7 @@ private[team] final class Cached {
def name(id: String) = nameCache get id
private[team] val teamIdsCache = MixedCache[String, Set[String]](
name = "team.ids",
MemberRepo.teamIdsByUser,
timeToLive = 2 hours,
default = _ => Set.empty,
@ -20,7 +23,8 @@ private[team] final class Cached {
def teamIds(userId: String) = teamIdsCache get userId
val nbRequests = AsyncCache(
(userId: String) => TeamRepo teamIdsByCreator userId flatMap RequestRepo.countByTeams,
val nbRequests = AsyncCache[String, Int](
name = "team.nbRequests",
f = userId => TeamRepo teamIdsByCreator userId flatMap RequestRepo.countByTeams,
maxCapacity = 20000)
}

View file

@ -9,6 +9,7 @@ private[tournament] final class Cached(
rankingTtl: FiniteDuration) {
private val nameCache = MixedCache[String, Option[String]](
name = "tournament.name",
((id: String) => TournamentRepo byId id map2 { (tour: Tournament) => tour.fullName }),
timeToLive = 6 hours,
default = _ => none,
@ -17,6 +18,7 @@ private[tournament] final class Cached(
def name(id: String): Option[String] = nameCache get id
val promotable = AsyncCache.single(
name = "tournament.promotable",
TournamentRepo.promotable,
timeToLive = createdTtl)
@ -36,11 +38,13 @@ private[tournament] final class Cached(
// only applies to ongoing tournaments
private val ongoingRanking = AsyncCache[String, Ranking](
PlayerRepo.computeRanking,
name = "tournament.ongoingRanking",
f = PlayerRepo.computeRanking,
timeToLive = 3.seconds)
// only applies to finished tournaments
private val finishedRanking = AsyncCache[String, Ranking](
PlayerRepo.computeRanking,
name = "tournament.finishedRanking",
f = PlayerRepo.computeRanking,
timeToLive = rankingTtl)
}

View file

@ -1,7 +1,7 @@
package lila.tournament
import org.joda.time.format.ISODateTimeFormat
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import play.api.libs.json._
import scala.concurrent.duration._
@ -174,21 +174,24 @@ final class JsonView(
)
private val firstPageCache = lila.memo.AsyncCache[String, JsObject](
(id: String) => TournamentRepo byId id flatten s"No such tournament: $id" flatMap { computeStanding(_, 1) },
name = "tournament.firstPage",
id => TournamentRepo byId id flatten s"No such tournament: $id" flatMap { computeStanding(_, 1) },
timeToLive = 1 second)
private val cachableData = lila.memo.AsyncCache[String, CachableData](id =>
for {
pairings <- PairingRepo.recentByTour(id, 40)
tour <- TournamentRepo byId id
featured <- tour ?? fetchFeaturedGame
podium <- podiumJson(id)
next <- tour.filter(_.isFinished) ?? cached.findNext map2 nextJson
} yield CachableData(
pairings = JsArray(pairings map pairingJson),
featured = featured map featuredJson,
podium = podium,
next = next),
private val cachableData = lila.memo.AsyncCache[String, CachableData](
name = "tournament.json.cachable",
id =>
for {
pairings <- PairingRepo.recentByTour(id, 40)
tour <- TournamentRepo byId id
featured <- tour ?? fetchFeaturedGame
podium <- podiumJson(id)
next <- tour.filter(_.isFinished) ?? cached.findNext map2 nextJson
} yield CachableData(
pairings = JsArray(pairings map pairingJson),
featured = featured map featuredJson,
podium = podium,
next = next),
timeToLive = 1 second)
private def nextJson(tour: Tournament) = Json.obj(

View file

@ -310,7 +310,8 @@ final class TournamentApi(
}
private val miniStandingCache = lila.memo.AsyncCache[String, List[RankedPlayer]](
(id: String) => PlayerRepo.bestByTourWithRank(id, 30),
name = "tournament.miniStanding",
id => PlayerRepo.bestByTourWithRank(id, 30),
timeToLive = 3 second)
def miniStanding(tourId: String, withStanding: Boolean): Fu[Option[MiniStanding]] =

View file

@ -51,6 +51,7 @@ final class Env(
object isStreamer {
private val cache = lila.memo.MixedCache.single[Set[String]](
name = "tv.streamers",
f = streamerList.lichessIds,
timeToLive = 10 seconds,
default = Set.empty,
@ -60,6 +61,7 @@ final class Env(
object streamsOnAir {
private val cache = lila.memo.AsyncCache.single[List[StreamOnAir]](
name = "tv.streamsOnAir",
f = streaming.onAir,
timeToLive = 2 seconds)
def all = cache(true)

View file

@ -85,6 +85,7 @@ final class Cached(
keyToString = _.toString)
val top50Online = lila.memo.AsyncCache.single[List[User]](
name = "user.top50online",
f = UserRepo.byIdsSortRating(onlineUserIdMemo.keys, 50),
timeToLive = 10 seconds)

View file

@ -26,6 +26,7 @@ final class LightUserApi(coll: Coll) {
}
private val cache = lila.memo.MixedCache[String, Option[LightUser]](
name = "user.light",
id => coll.find(
$id(id),
$doc(F.username -> true, F.title -> true, s"${F.plan}.active" -> true)

View file

@ -78,6 +78,7 @@ final class RankingApi(
} map (_.flatten.toMap)
private val cache = AsyncCache[Perf.ID, Map[User.ID, Rank]](
name = "rankingApi.weeklyStableRanking",
f = compute,
timeToLive = 15 minutes)

View file

@ -49,14 +49,14 @@ private[video] final class VideoApi(
val textScore = $doc("score" -> $doc("$meta" -> "textScore"))
Paginator(
adapter = new Adapter[Video](
collection = videoColl,
selector = $doc(
"$text" -> $doc("$search" -> q)
),
projection = textScore,
sort = textScore,
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
collection = videoColl,
selector = $doc(
"$text" -> $doc("$search" -> q)
),
projection = textScore,
sort = textScore,
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
}
@ -82,12 +82,12 @@ private[video] final class VideoApi(
def popular(user: Option[User], page: Int): Fu[Paginator[VideoView]] = Paginator(
adapter = new Adapter[Video](
collection = videoColl,
selector = $empty,
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
collection = videoColl,
selector = $empty,
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
@ -95,24 +95,24 @@ private[video] final class VideoApi(
if (tags.isEmpty) popular(user, page)
else Paginator(
adapter = new Adapter[Video](
collection = videoColl,
selector = $doc("tags" $all tags),
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
collection = videoColl,
selector = $doc("tags" $all tags),
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
def byAuthor(user: Option[User], author: String, page: Int): Fu[Paginator[VideoView]] =
Paginator(
adapter = new Adapter[Video](
collection = videoColl,
selector = $doc("author" -> author),
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
collection = videoColl,
selector = $doc("author" -> author),
projection = $empty,
sort = $doc("metadata.likes" -> -1),
readPreference = ReadPreference.secondaryPreferred
) mapFutureList videoViews(user),
currentPage = page,
maxPerPage = maxPerPage)
@ -129,6 +129,7 @@ private[video] final class VideoApi(
object count {
private val cache = AsyncCache.single(
name = "video.count",
f = videoColl.count(none),
timeToLive = 1.day)
@ -154,10 +155,11 @@ private[video] final class VideoApi(
).some) map (0!=)
def seenVideoIds(user: User, videos: Seq[Video]): Fu[Set[Video.ID]] =
viewColl.distinct[String, Set](View.BSONFields.videoId,
viewColl.distinct[String, Set](
View.BSONFields.videoId,
$inIds(videos.map { v =>
View.makeId(v.id, user.id)
}).some)
View.makeId(v.id, user.id)
}).some)
}
object tag {
@ -173,37 +175,39 @@ private[video] final class VideoApi(
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework.{ Descending, GroupField, Match, Project, UnwindField, Sort, SumValue }
private val pathsCache = AsyncCache[List[Tag], List[TagNb]](
name = "video.paths",
f = filterTags => {
val allPaths =
if (filterTags.isEmpty) allPopular map { tags =>
tags.filterNot(_.isNumeric)
}
else videoColl.aggregate(
Match($doc("tags" $all filterTags)),
List(Project($doc("tags" -> true)), UnwindField("tags"),
GroupField("tags")("nb" -> SumValue(1)))).map(
_.firstBatch.flatMap(_.asOpt[TagNb]))
allPopular zip allPaths map {
case (all, paths) =>
val tags = all map { t =>
paths find (_._id == t._id) getOrElse TagNb(t._id, 0)
} filterNot (_.empty) take max
val missing = filterTags filterNot { t =>
tags exists (_.tag == t)
}
val list = tags.take(max - missing.size) ::: missing.flatMap { t =>
all find (_.tag == t)
}
list.sortBy { t =>
if (filterTags contains t.tag) Int.MinValue
else -t.nb
}
val allPaths =
if (filterTags.isEmpty) allPopular map { tags =>
tags.filterNot(_.isNumeric)
}
},
else videoColl.aggregate(
Match($doc("tags" $all filterTags)),
List(Project($doc("tags" -> true)), UnwindField("tags"),
GroupField("tags")("nb" -> SumValue(1)))).map(
_.firstBatch.flatMap(_.asOpt[TagNb]))
allPopular zip allPaths map {
case (all, paths) =>
val tags = all map { t =>
paths find (_._id == t._id) getOrElse TagNb(t._id, 0)
} filterNot (_.empty) take max
val missing = filterTags filterNot { t =>
tags exists (_.tag == t)
}
val list = tags.take(max - missing.size) ::: missing.flatMap { t =>
all find (_.tag == t)
}
list.sortBy { t =>
if (filterTags contains t.tag) Int.MinValue
else -t.nb
}
}
},
maxCapacity = 100)
private val popularCache = AsyncCache.single[List[TagNb]](
name = "video.popular",
f = videoColl.aggregate(
Project($doc("tags" -> true)), List(
UnwindField("tags"), GroupField("tags")("nb" -> SumValue(1)),