migration WIP

rm0193-mapreduce
Thibault Duplessis 2019-11-30 16:45:44 -06:00
parent e0819404db
commit 209d3fed95
24 changed files with 220 additions and 238 deletions

View File

@ -24,7 +24,7 @@ PlayKeys.externalizeResources := false
scriptClasspath := Seq("*")
// offline := true
libraryDependencies ++= Seq(
macwire, play.json, jodaForms, ws,
macwire.macros, macwire.util, play.json, jodaForms, ws,
scalaz, chess, compression, scalalib, hasher,
reactivemongo.driver, reactivemongo.bson, reactivemongo.native,
maxmind, prismic, markdown, scalatags,

View File

@ -518,7 +518,6 @@ studySearch {
paginator.max_per_page = ${study.paginator.max_per_page}
}
user {
paginator.max_per_page = 40
cached.nb.ttl = 10 minutes
online.ttl = 7 seconds
collection {

View File

@ -11,10 +11,11 @@ case class Bookmark(game: lila.game.Game, user: lila.user.User)
final class BookmarkApi(
coll: Coll,
gameRepo: GameRepo,
paginator: PaginatorBuilder
) {
private def exists(gameId: String, userId: User.ID): Fu[Boolean] =
private def exists(gameId: Game.ID, userId: User.ID): Fu[Boolean] =
coll exists selectId(gameId, userId)
def exists(game: Game, user: User): Fu[Boolean] =
@ -24,29 +25,27 @@ final class BookmarkApi(
def exists(game: Game, user: Option[User]): Fu[Boolean] =
user.?? { exists(game, _) }
def filterGameIdsBookmarkedBy(games: Seq[Game], user: Option[User]): Fu[Set[String]] =
def filterGameIdsBookmarkedBy(games: Seq[Game], user: Option[User]): Fu[Set[Game.ID]] =
user ?? { u =>
val candidateIds = games.filter(_.bookmarks > 0).map(_.id)
if (candidateIds.isEmpty) fuccess(Set.empty)
else coll.distinct[String, Set]("g", Some(
userIdQuery(u.id) ++ $doc("g" $in candidateIds)
))
val candidateIds = games collect { case g if g.bookmarks > 0 => g.id }
candidateIds.nonEmpty ??
coll.distinctEasy[Game.ID, Set]("g", userIdQuery(u.id) ++ $doc("g" $in candidateIds))
}
def removeByGameId(gameId: String): Funit =
def removeByGameId(gameId: Game.ID): Funit =
coll.remove($doc("g" -> gameId)).void
def removeByGameIds(gameIds: List[String]): Funit =
def removeByGameIds(gameIds: List[Game.ID]): Funit =
coll.remove($doc("g" $in gameIds)).void
def remove(gameId: String, userId: User.ID): Funit = coll.remove(selectId(gameId, userId)).void
def remove(gameId: Game.ID, userId: User.ID): Funit = coll.remove(selectId(gameId, userId)).void
// def remove(selector: Bdoc): Funit = coll.remove(selector).void
def toggle(gameId: String, userId: User.ID): Funit =
def toggle(gameId: Game.ID, userId: User.ID): Funit =
exists(gameId, userId) flatMap { e =>
(if (e) remove(gameId, userId) else add(gameId, userId, DateTime.now)) inject !e
} flatMap { bookmarked =>
GameRepo.incBookmarks(gameId, if (bookmarked) 1 else -1)
gameRepo.incBookmarks(gameId, if (bookmarked) 1 else -1)
}
def countByUser(user: User): Fu[Int] = coll.countSel(userIdQuery(user.id))
@ -54,7 +53,7 @@ final class BookmarkApi(
def gamePaginatorByUser(user: User, page: Int) =
paginator.byUser(user, page) map2 { (b: Bookmark) => b.game }
private def add(gameId: String, userId: User.ID, date: DateTime): Funit =
private def add(gameId: Game.ID, userId: User.ID, date: DateTime): Funit =
coll.insert($doc(
"_id" -> makeId(gameId, userId),
"g" -> gameId,
@ -63,6 +62,6 @@ final class BookmarkApi(
)).void
private def userIdQuery(userId: User.ID) = $doc("u" -> userId)
private def makeId(gameId: String, userId: User.ID) = s"$gameId$userId"
private def selectId(gameId: String, userId: User.ID) = $id(makeId(gameId, userId))
private def makeId(gameId: Game.ID, userId: User.ID) = s"$gameId$userId"
private def selectId(gameId: Game.ID, userId: User.ID) = $id(makeId(gameId, userId))
}

View File

@ -1,45 +1,41 @@
package lila.bookmark
import akka.actor._
import com.typesafe.config.Config
import com.softwaremill.macwire._
import io.methvin.play.autoconfig._
import play.api.Configuration
import lila.common.config._
import lila.common.tagging._
import lila.hub.actorApi.bookmark._
@Module
class BookmarkConfig(
@ConfigName("collection.bookmark") val bookmarkCollName: CollName,
@ConfigName("paginator.maxPerPage") val paginatorMaxPerPage: MaxPerPage,
@ConfigName("actor.name") val actorName: String
)
trait PagMax
final class Env(
config: Config,
system: ActorSystem,
db: lila.db.Env
) {
appConfig: Configuration,
db: lila.db.Env,
gameRepo: lila.game.GameRepo
)(implicit system: ActorSystem) {
private val CollectionBookmark = config getString "collection.bookmark"
private val PaginatorMaxPerPage = config getInt "paginator.max_per_page"
private val ActorName = config getString "actor.name"
private val config = appConfig.get[BookmarkConfig]("game")(AutoConfig.loader)
private[bookmark] lazy val bookmarkColl = db(CollectionBookmark)
private lazy val bookmarkColl = db(config.bookmarkCollName)
lazy val paginator = new PaginatorBuilder(
coll = bookmarkColl,
maxPerPage = lila.common.MaxPerPage(PaginatorMaxPerPage)
)
lazy val paginator = wire[PaginatorBuilder]
lazy val api = new BookmarkApi(
coll = bookmarkColl,
paginator = paginator
)
lazy val api = wire[BookmarkApi]
system.actorOf(Props(new Actor {
def receive = {
case Toggle(gameId, userId) => api.toggle(gameId, userId)
case Remove(gameId) => api removeByGameId gameId
}
}), name = ActorName)
}
object Env {
lazy val current = "bookmark" boot new Env(
config = lila.common.PlayApp loadConfig "bookmark",
system = lila.common.PlayApp.system,
db = lila.db.Env.current
)
}), name = config.actorName)
}

View File

@ -7,7 +7,8 @@ import lila.user.User
private[bookmark] final class PaginatorBuilder(
coll: Coll,
maxPerPage: lila.common.MaxPerPage
gameRepo: GameRepo,
maxPerPage: lila.common.config.MaxPerPage
) {
def byUser(user: User, page: Int): Fu[Paginator[Bookmark]] =
@ -25,12 +26,12 @@ private[bookmark] final class PaginatorBuilder(
def nbResults: Fu[Int] = coll countSel selector
def slice(offset: Int, length: Int): Fu[Seq[Bookmark]] = for {
gameIds <- coll.find(selector, $doc("g" -> true))
gameIds <- coll.find(selector, $doc("g" -> true).some)
.sort(sorting)
.skip(offset)
.cursor[Bdoc]()
.gather[List](length) map { _ flatMap { _.getAs[String]("g") } }
games <- GameRepo gamesFromSecondary gameIds
.gather[List](length) map { _ flatMap { _.string("g") } }
games <- gameRepo gamesFromSecondary gameIds
} yield games map { g => Bookmark(g, user) }
private def selector = $doc("u" -> user.id)

View File

@ -15,6 +15,9 @@ object config {
case class AppPath(value: String) extends AnyVal with StringValue
case class Max(value: Int) extends AnyVal with IntValue with Ordered[Int] {
def compare(other: Int) = Integer.compare(value, other)
}
case class MaxPerPage(value: Int) extends AnyVal with IntValue
case class MaxPerSecond(value: Int) extends AnyVal with IntValue
@ -26,6 +29,7 @@ object config {
email: EmailAddress
)
implicit val maxLoader = intLoader(Max.apply)
implicit val maxPerPageLoader = intLoader(MaxPerPage.apply)
implicit val maxPerSecondLoader = intLoader(MaxPerSecond.apply)
implicit val collNameLoader = strLoader(CollName.apply)

View File

@ -1,4 +1,4 @@
package lila
package lila.common
/**
* Tag instances with arbitrary types. The tags are usually empty `trait`s. Tags have no runtime overhead and are only

View File

@ -177,14 +177,6 @@ trait CollExt { self: dsl with QueryBuilderExt =>
readPreference = readPreference
)(f).collect[List](maxDocs = maxDocs, Cursor.FailOnError[List[Bdoc]]())
def aggregateOne(
firstOperator: coll.PipelineOperator,
otherOperators: List[coll.PipelineOperator] = Nil,
readPreference: ReadPreference = ReadPreference.primary
): Fu[Option[Bdoc]] =
coll.aggregatorContext[Bdoc](firstOperator, otherOperators, readPreference = readPreference)
.prepared(CursorProducer.defaultCursorProducer[Bdoc]).cursor.headOption
def distinctEasy[T, M[_] <: Iterable[_]](
key: String,
selector: coll.pack.Document

View File

@ -10,16 +10,17 @@ import scala.concurrent.duration._
import lila.common.config._
private case class GameConfig(
@ConfigName("collection.game") gameColl: CollName,
@ConfigName("collection.crosstable") crosstableColl: CollName,
@ConfigName("collection.matchup") matchupColl: CollName,
@ConfigName("paginator.maxPerPage") paginatorMaxPerPage: MaxPerPage,
@ConfigName("captcher.name") captcherName: String,
@ConfigName("captcher.duration") captcherDuration: FiniteDuration,
uciMemoTtl: FiniteDuration,
pngUrl: String,
pngSize: Int
@Module
private class GameConfig(
@ConfigName("collection.game") val gameColl: CollName,
@ConfigName("collection.crosstable") val crosstableColl: CollName,
@ConfigName("collection.matchup") val matchupColl: CollName,
@ConfigName("paginator.maxPerPage") val paginatorMaxPerPage: MaxPerPage,
@ConfigName("captcher.name") val captcherName: String,
@ConfigName("captcher.duration") val captcherDuration: FiniteDuration,
val uciMemoTtl: FiniteDuration,
val pngUrl: String,
val pngSize: Int
)
final class Env(
@ -36,28 +37,26 @@ final class Env(
)(implicit system: ActorSystem, scheduler: Scheduler) {
private val config = appConfig.get[GameConfig]("game")(AutoConfig.loader)
import config._
lazy val gameRepo = new GameRepo(db(gameColl))
lazy val gameRepo = new GameRepo(db(config.gameColl))
lazy val pngExport = new PngExport(ws, pngUrl, pngSize)
lazy val pngExport = new PngExport(ws, config.pngUrl, config.pngSize)
lazy val divider = wire[Divider]
lazy val cached: Cached = wire[Cached]
lazy val paginator = new PaginatorBuilder(gameRepo, cached, paginatorMaxPerPage)
// lazy val paginator = wire[PaginatorBuilder]
lazy val paginator = wire[PaginatorBuilder]
lazy val rewind = wire[Rewind]
lazy val uciMemo = new UciMemo(gameRepo, uciMemoTtl)
lazy val uciMemo = new UciMemo(gameRepo, config.uciMemoTtl)
lazy val pgnDump = wire[PgnDump]
lazy val crosstableApi = new CrosstableApi(
coll = db(crosstableColl),
matchupColl = db(matchupColl),
coll = db(config.crosstableColl),
matchupColl = db(config.matchupColl),
userRepo = userRepo,
gameRepo = gameRepo,
asyncCache = asyncCache
@ -76,8 +75,8 @@ final class Env(
lazy val jsonView = new JsonView(rematchOf = rematches.getIfPresent)
// eargerly load captcher actor
private val captcher = system.actorOf(Props(new Captcher(gameRepo)), name = captcherName)
scheduler.scheduleWithFixedDelay(captcherDuration, captcherDuration) {
private val captcher = system.actorOf(Props(new Captcher(gameRepo)), name = config.captcherName)
scheduler.scheduleWithFixedDelay(config.captcherDuration, config.captcherDuration) {
() => captcher ! actorApi.NewCaptcha
}
}

View File

@ -7,7 +7,6 @@ import scala.concurrent.duration._
import lila.db.BSON.BSONJodaDateTimeHandler
import lila.db.dsl._
import lila.tagging._
final class MongoCache[K, V: BSONHandler] private (
prefix: String,

View File

@ -14,15 +14,15 @@ final class PersonalTokenApi(
import AccessToken.{ BSONFields => F, _ }
def list(u: User): Fu[List[AccessToken]] =
tokenColl.find($doc(
tokenColl.ext.find($doc(
F.userId -> u.id,
F.clientId -> clientId
)).sort($sort desc F.createdAt).list[AccessToken](100)
def create(token: AccessToken) = tokenColl insert token void
def create(token: AccessToken) = tokenColl.insert.one(token).void
def deleteBy(tokenId: AccessToken.Id, user: User) =
tokenColl.remove($doc(
tokenColl.delete.one($doc(
F.id -> tokenId,
F.clientId -> clientId,
F.userId -> user.id

View File

@ -4,7 +4,6 @@ import io.methvin.play.autoconfig._
import play.api.Configuration
import scala.concurrent.duration._
import lila.common.CollName
import lila.common.config._
case class PrefConfig(

View File

@ -1,79 +1,63 @@
package lila.relation
import akka.actor._
import com.typesafe.config.Config
import com.softwaremill.macwire._
import io.methvin.play.autoconfig._
import play.api.Configuration
import scala.concurrent.duration._
import lila.common.config._
@Module
private class RelationConfig(
@ConfigName("collection.relation") val collection: CollName,
@ConfigName("actor.notify_freq") val actorNotifyFreq: FiniteDuration,
@ConfigName("actor.name") val actorName: String,
@ConfigName("limit.follow") val maxFollow: Max,
@ConfigName("limit.block") val maxBlock: Max
)
final class Env(
config: Config,
appConfig: Configuration,
db: lila.db.Env,
hub: lila.hub.Env,
onlineUserIds: () => Set[lila.user.User.ID],
lightUserApi: lila.user.LightUserApi,
followable: String => Fu[Boolean],
system: ActorSystem,
asyncCache: lila.memo.AsyncCache.Builder,
scheduler: lila.common.Scheduler
) {
asyncCache: lila.memo.AsyncCache.Builder
)(implicit system: ActorSystem) {
private val settings = new {
val CollectionRelation = config getString "collection.relation"
val ActorNotifyFreq = config duration "actor.notify_freq"
val ActorName = config getString "actor.name"
val MaxBlock = config getInt "limit.block"
}
import settings._
private val config = appConfig.get[RelationConfig]("relation")(AutoConfig.loader)
val MaxFollow = config getInt "limit.follow"
def maxFollow = config.maxFollow
private[relation] val coll = db(CollectionRelation)
private lazy val coll = db(config.collection)
private lazy val repo: RelationRepo = wire[RelationRepo]
lazy val api = new RelationApi(
coll = coll,
repo = repo,
actor = hub.relation,
timeline = hub.timeline,
reporter = hub.report,
followable = followable,
asyncCache = asyncCache,
maxFollow = MaxFollow,
maxBlock = MaxBlock
maxFollow = config.maxFollow,
maxBlock = config.maxBlock
)
lazy val stream = new RelationStream(coll = coll)(system)
lazy val stream = wire[RelationStream]
val online = new OnlineDoing(
api,
lightUser = lightUserApi.sync,
onlineUserIds
)
lazy val online: OnlineDoing = wire[OnlineDoing]
def isPlaying(userId: lila.user.User.ID): Boolean =
online.playing.get(userId)
def isPlaying(userId: lila.user.User.ID): Boolean = online.playing.get(userId)
private[relation] val actor = system.actorOf(Props(new RelationActor(
lightUser = lightUserApi.sync,
api = api,
online = online
)), name = ActorName)
private val lightSync = lightUserApi.sync _
scheduler.once(15 seconds) {
scheduler.message(ActorNotifyFreq) {
actor -> actorApi.ComputeMovement
}
private[relation] val actor = system.actorOf(Props(wire[RelationActor]), name = config.actorName)
system.scheduler.scheduleWithFixedDelay(15 seconds, config.actorNotifyFreq) {
() => actor ! actorApi.ComputeMovement
}
}
object Env {
lazy val current = "relation" boot new Env(
config = lila.common.PlayApp loadConfig "relation",
db = lila.db.Env.current,
hub = lila.hub.Env.current,
onlineUserIds = lila.socket.Env.current.onlineUserIds,
lightUserApi = lila.user.Env.current.lightUserApi,
followable = lila.pref.Env.current.api.followable _,
system = lila.common.PlayApp.system,
asyncCache = lila.memo.Env.current.asyncCache,
scheduler = lila.common.PlayApp.scheduler
)
}

View File

@ -36,7 +36,7 @@ final class OnlineDoing(
api fetchFollowing userId map userIds().intersect map { friends =>
if (friends.isEmpty) OnlineFriends.empty
else OnlineFriends(
users = friends.flatMap { lightUser(_) }(scala.collection.breakOut),
users = friends.view.flatMap { lightUser(_) }.to(List),
playing = playing intersect friends,
studying = friends filter studying.getAllPresent(friends).contains
)

View File

@ -1,7 +1,6 @@
package lila.relation
import akka.actor.Actor
import scala.collection.breakOut
import actorApi._
import lila.common.{ Bus, LightUser }
@ -32,8 +31,8 @@ private[relation] final class RelationActor(
case ComputeMovement =>
val curIds = online.userIds()
val leaveUsers: List[LightUser] = (previousOnlineIds diff curIds).flatMap { lightUser(_) }(breakOut)
val enterUsers: List[LightUser] = (curIds diff previousOnlineIds).flatMap { lightUser(_) }(breakOut)
val leaveUsers: List[LightUser] = (previousOnlineIds diff curIds).view.flatMap { lightUser(_) }.to(List)
val enterUsers: List[LightUser] = (curIds diff previousOnlineIds).view.flatMap { lightUser(_) }.to(List)
val friendsEntering = enterUsers map { u =>
FriendEntering(u, online.playing get u.id, online isStudying u.id)
@ -132,7 +131,7 @@ private[relation] final class RelationActor(
}
}
private def notifyFollowersGameStateChanged(userIds: Traversable[ID], message: String) =
private def notifyFollowersGameStateChanged(userIds: Iterable[ID], message: String) =
userIds foreach { userId =>
api.fetchFollowersFromSecondary(userId) map online.userIds().intersect foreach { ids =>
if (ids.nonEmpty) Bus.publish(SendTos(ids.toSet, message, userId), "socketUsers")

View File

@ -1,28 +1,29 @@
package lila.relation
import akka.actor.ActorSelection
import reactivemongo.api._
import reactivemongo.api.bson._
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
import scala.concurrent.duration._
import BSONHandlers._
import lila.common.Bus
import lila.common.config.Max
import lila.db.dsl._
import lila.db.paginator._
import lila.hub.actorApi.timeline.{ Propagate, Follow => FollowUser }
import lila.user.User
import BSONHandlers._
import reactivemongo.api._
import reactivemongo.api.collections.bson.BSONBatchCommands.AggregationFramework._
import reactivemongo.api.bson._
final class RelationApi(
coll: Coll,
repo: RelationRepo,
actor: ActorSelection,
timeline: ActorSelection,
reporter: ActorSelection,
followable: ID => Fu[Boolean],
asyncCache: lila.memo.AsyncCache.Builder,
maxFollow: Int,
maxBlock: Int
maxFollow: Max,
maxBlock: Max
) {
import RelationRepo.makeId
@ -32,25 +33,29 @@ final class RelationApi(
}
def fetchRelation(u1: User, u2: User): Fu[Option[Relation]] = fetchRelation(u1.id, u2.id)
def fetchFollowing = RelationRepo following _
def fetchFollowing = repo following _
def fetchFollowersFromSecondary = RelationRepo.followersFromSecondary _
def fetchFollowersFromSecondary = repo.followersFromSecondary _
def fetchBlocking = RelationRepo blocking _
def fetchBlocking = repo blocking _
def fetchFriends(userId: ID) = coll.aggregateOne(Match($doc(
"$or" -> $arr($doc("u1" -> userId), $doc("u2" -> userId)),
"r" -> Follow
)), List(
Group(BSONNull)(
"u1" -> AddFieldToSet("u1"),
"u2" -> AddFieldToSet("u2")
),
Project($id($doc("$setIntersection" -> $arr("$u1", "$u2"))))
),
ReadPreference.secondaryPreferred).map {
~_.flatMap(_.getAs[Set[String]]("_id")) - userId
}
def fetchFriends(userId: ID) = coll.aggregateWith[Bdoc](
readPreference = ReadPreference.secondaryPreferred
) { framework =>
import framework._
Match($doc(
"$or" -> $arr($doc("u1" -> userId), $doc("u2" -> userId)),
"r" -> Follow
)) -> List(
Group(BSONNull)(
"u1" -> AddFieldToSet("u1"),
"u2" -> AddFieldToSet("u2")
),
Project($id($doc("$setIntersection" -> $arr("$u1", "$u2"))))
)
}.headOption.map {
~_.flatMap(_.getAsOpt[Set[String]]("_id")) - userId
}
def fetchFollows(u1: ID, u2: ID): Fu[Boolean] = (u1 != u2) ?? {
coll.exists($doc("_id" -> makeId(u1, u2), "r" -> Follow))
@ -82,16 +87,16 @@ final class RelationApi(
def countFollowers(userId: ID) = countFollowersCache get userId
def countBlocking(userId: ID) =
coll.count($doc("u1" -> userId, "r" -> Block).some)
coll.countSel($doc("u1" -> userId, "r" -> Block))
def countBlockers(userId: ID) =
coll.count($doc("u2" -> userId, "r" -> Block).some)
coll.countSel($doc("u2" -> userId, "r" -> Block))
def followingPaginatorAdapter(userId: ID) = new CachedAdapter[Followed](
adapter = new Adapter[Followed](
collection = coll,
selector = $doc("u1" -> userId, "r" -> Follow),
projection = $doc("u2" -> true, "_id" -> false),
projection = $doc("u2" -> true, "_id" -> false).some,
sort = $empty
),
nbResults = countFollowing(userId)
@ -101,7 +106,7 @@ final class RelationApi(
adapter = new Adapter[Follower](
collection = coll,
selector = $doc("u2" -> userId, "r" -> Follow),
projection = $doc("u1" -> true, "_id" -> false),
projection = $doc("u1" -> true, "_id" -> false).some,
sort = $empty
),
nbResults = countFollowers(userId)
@ -110,7 +115,7 @@ final class RelationApi(
def blockingPaginatorAdapter(userId: ID) = new Adapter[Blocked](
collection = coll,
selector = $doc("u1" -> userId, "r" -> Block),
projection = $doc("u2" -> true, "_id" -> false),
projection = $doc("u2" -> true, "_id" -> false).some,
sort = $empty
).map(_.userId)
@ -119,9 +124,9 @@ final class RelationApi(
case true => fetchRelation(u1, u2) zip fetchRelation(u2, u1) flatMap {
case (Some(Follow), _) => funit
case (_, Some(Block)) => funit
case _ => RelationRepo.follow(u1, u2) >> limitFollow(u1) >>- {
case _ => repo.follow(u1, u2) >> limitFollow(u1) >>- {
countFollowersCache.update(u2, 1+)
countFollowingCache.update(u1, prev => (prev + 1) atMost maxFollow)
countFollowingCache.update(u1, prev => (prev + 1) atMost maxFollow.value)
reloadOnlineFriends(u1, u2)
timeline ! Propagate(FollowUser(u1, u2)).toFriendsOf(u1).toUsers(List(u2))
Bus.publish(lila.hub.actorApi.relation.Follow(u1, u2), "relation")
@ -131,18 +136,18 @@ final class RelationApi(
}
private def limitFollow(u: ID) = countFollowing(u) flatMap { nb =>
(nb > maxFollow) ?? RelationRepo.drop(u, true, nb - maxFollow)
(maxFollow < nb) ?? repo.drop(u, true, nb - maxFollow.value)
}
private def limitBlock(u: ID) = countBlocking(u) flatMap { nb =>
(nb > maxBlock) ?? RelationRepo.drop(u, false, nb - maxBlock)
(maxBlock < nb) ?? repo.drop(u, false, nb - maxBlock.value)
}
def block(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
fetchBlocks(u1, u2) flatMap {
case true => funit
case _ =>
RelationRepo.block(u1, u2) >> limitBlock(u1) >> unfollow(u2, u1) >>- {
repo.block(u1, u2) >> limitBlock(u1) >> unfollow(u2, u1) >>- {
reloadOnlineFriends(u1, u2)
Bus.publish(lila.hub.actorApi.relation.Block(u1, u2), "relation")
lila.mon.relation.block()
@ -152,7 +157,7 @@ final class RelationApi(
def unfollow(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
fetchFollows(u1, u2) flatMap {
case true => RelationRepo.unfollow(u1, u2) >>- {
case true => repo.unfollow(u1, u2) >>- {
countFollowersCache.update(u2, _ - 1)
countFollowingCache.update(u1, _ - 1)
reloadOnlineFriends(u1, u2)
@ -162,11 +167,11 @@ final class RelationApi(
}
}
def unfollowAll(u1: ID): Funit = RelationRepo.unfollowAll(u1)
def unfollowAll(u1: ID): Funit = repo.unfollowAll(u1)
def unblock(u1: ID, u2: ID): Funit = (u1 != u2) ?? {
fetchBlocks(u1, u2) flatMap {
case true => RelationRepo.unblock(u1, u2) >>- {
case true => repo.unblock(u1, u2) >>- {
reloadOnlineFriends(u1, u2)
Bus.publish(lila.hub.actorApi.relation.UnBlock(u1, u2), "relation")
lila.mon.relation.unblock()
@ -176,7 +181,7 @@ final class RelationApi(
}
def searchFollowedBy(u: User, term: String, max: Int): Fu[List[User.ID]] =
RelationRepo.followingLike(u.id, term) map { _.sorted take max }
repo.followingLike(u.id, term) map { _.sorted take max }
private def reloadOnlineFriends(u1: ID, u2: ID): Unit = {
import lila.hub.actorApi.relation.ReloadOnlineFriends

View File

@ -1,14 +1,13 @@
package lila.relation
import reactivemongo.api.ReadPreference
import reactivemongo.api.bson._
import reactivemongo.api.ReadPreference
import lila.db.dsl._
private[relation] object RelationRepo {
private final class RelationRepo(coll: Coll) {
// dirty
private val coll = Env.current.coll
import RelationRepo._
def following(userId: ID) = relating(userId, Follow)
@ -19,51 +18,53 @@ private[relation] object RelationRepo {
def followingLike(userId: ID, term: String): Fu[List[ID]] =
lila.user.User.couldBeUsername(term) ?? {
coll.distinctWithReadPreference[ID, List]("u2", $doc(
coll.secondaryPreferred.distinctEasy[ID, List]("u2", $doc(
"u1" -> userId,
"u2" $startsWith term.toLowerCase,
"r" -> Follow
).some,
ReadPreference.secondaryPreferred)
))
}
private def relaters(userId: ID, relation: Relation, rp: ReadPreference = ReadPreference.primary): Fu[Set[ID]] =
coll.distinctWithReadPreference[String, Set]("u1", $doc(
coll.withReadPreference(rp).distinctEasy[String, Set]("u1", $doc(
"u2" -> userId,
"r" -> relation
).some, rp)
))
private def relating(userId: ID, relation: Relation): Fu[Set[ID]] =
coll.distinct[String, Set]("u2", $doc(
coll.distinctEasy[String, Set]("u2", $doc(
"u1" -> userId,
"r" -> relation
).some)
))
def follow(u1: ID, u2: ID): Funit = save(u1, u2, Follow)
def unfollow(u1: ID, u2: ID): Funit = remove(u1, u2)
def block(u1: ID, u2: ID): Funit = save(u1, u2, Block)
def unblock(u1: ID, u2: ID): Funit = remove(u1, u2)
def unfollowAll(u1: ID): Funit = coll.remove($doc("u1" -> u1)).void
def unfollowAll(u1: ID): Funit = coll.delete.one($doc("u1" -> u1)).void
private def save(u1: ID, u2: ID, relation: Relation): Funit = coll.update(
private def save(u1: ID, u2: ID, relation: Relation): Funit = coll.update.one(
$id(makeId(u1, u2)),
$doc("u1" -> u1, "u2" -> u2, "r" -> relation),
upsert = true
).void
def remove(u1: ID, u2: ID): Funit = coll.remove($id(makeId(u1, u2))).void
def remove(u1: ID, u2: ID): Funit = coll.delete.one($id(makeId(u1, u2))).void
def drop(userId: ID, relation: Relation, nb: Int) =
coll.find(
$doc("u1" -> userId, "r" -> relation),
$doc("_id" -> true)
$doc("_id" -> true).some
)
.list[Bdoc](nb).map {
_.flatMap { _.getAs[String]("_id") }
_.flatMap { _.string("_id") }
} flatMap { ids =>
coll.remove($inIds(ids)).void
coll.delete.one($inIds(ids)).void
}
def makeId(u1: String, u2: String) = s"$u1/$u2"
}
object RelationRepo {
def makeId(u1: ID, u2: ID) = s"$u1/$u2"
}

View File

@ -1,37 +1,35 @@
package lila.relation
import play.api.libs.iteratee._
import reactivemongo.api.ReadPreference
import reactivemongo.play.iteratees.cursorProducer
import scala.concurrent.duration._
import lila.common.MaxPerSecond
import lila.common.config.MaxPerSecond
import lila.db.dsl._
import lila.user.{ User, UserRepo }
final class RelationStream(coll: Coll)(implicit system: akka.actor.ActorSystem) {
import RelationStream._
// import RelationStream._
def follow(user: User, direction: Direction, perSecond: MaxPerSecond): Enumerator[User] = {
val field = direction match {
case Direction.Following => "u2"
case Direction.Followers => "u1"
}
val projection = $doc(field -> true, "_id" -> false)
val query = direction match {
case Direction.Following => coll.find($doc("u1" -> user.id, "r" -> Follow), projection)
case Direction.Followers => coll.find($doc("u2" -> user.id, "r" -> Follow), projection)
}
query.copy(options = query.options.batchSize(perSecond.value))
.cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred)
.bulkEnumerator() &>
lila.common.Iteratee.delay(1 second) &>
Enumeratee.mapM { docs =>
UserRepo usersFromSecondary docs.toSeq.flatMap(_.getAs[User.ID](field))
} &>
Enumeratee.mapConcat(_.toSeq)
}
// def follow(user: User, direction: Direction, perSecond: MaxPerSecond): Enumerator[User] = {
// val field = direction match {
// case Direction.Following => "u2"
// case Direction.Followers => "u1"
// }
// val projection = $doc(field -> true, "_id" -> false)
// val query = direction match {
// case Direction.Following => coll.find($doc("u1" -> user.id, "r" -> Follow), projection)
// case Direction.Followers => coll.find($doc("u2" -> user.id, "r" -> Follow), projection)
// }
// query.copy(options = query.options.batchSize(perSecond.value))
// .cursor[Bdoc](readPreference = ReadPreference.secondaryPreferred)
// .bulkEnumerator() &>
// lila.common.Iteratee.delay(1 second) &>
// Enumeratee.mapM { docs =>
// UserRepo usersFromSecondary docs.toSeq.flatMap(_.getAs[User.ID](field))
// } &>
// Enumeratee.mapConcat(_.toSeq)
// }
}
object RelationStream {

View File

@ -41,7 +41,7 @@ object RoomSocket {
case chatApi.OnReinstate(userId) =>
this ! NotifyVersion("chat_reinstate", userId, false)
}
override def stop() {
override def stop() = {
super.stop()
send(Protocol.Out.stop(roomId))
chat foreach { c =>
@ -55,7 +55,7 @@ object RoomSocket {
mkTrouper = roomId => new RoomState(
RoomId(roomId),
send,
chatBus option RoomChat(Chat classify Chat.Id(roomId))
chatBus option RoomChat(Chat chanOf Chat.Id(roomId))
),
accessTimeout = 5 minutes
)

View File

@ -6,21 +6,21 @@ import io.methvin.play.autoconfig._
import play.api.Configuration
import play.api.libs.ws._
case class SearchConfig(
enabled: Boolean,
writeable: Boolean,
endpoint: String
@Module
private class SearchConfig(
val enabled: Boolean,
val writeable: Boolean,
val endpoint: String
)
final class Env(
appConfig: Configuration,
system: ActorSystem,
ws: WSClient
) {
)(implicit system: ActorSystem) {
private val config = appConfig.get[SearchConfig]("search")(AutoConfig.loader)
def makeHttp(index: Index): ESClientHttp = wire[ESClientHttp]
private def makeHttp(index: Index): ESClientHttp = wire[ESClientHttp]
val makeClient = (index: Index) =>
if (config.enabled) makeHttp(index)

View File

@ -1,6 +1,6 @@
package lila.search
import lila.common.MaxPerPage
import lila.common.config.MaxPerPage
import lila.common.paginator._
import play.api.libs.json.Writes

View File

@ -11,16 +11,15 @@ import lila.common.config._
import lila.common.LightUser
import lila.db.dsl.Coll
case class UserConfig(
@ConfigName("paginator.max_per_page") paginatorMaxPerPage: MaxPerPage,
@ConfigName("cached.nb.ttl") cachedNbTtl: FiniteDuration,
@ConfigName("online.ttl") onlineTtl: FiniteDuration,
@ConfigName("collection.user") collectionUser: CollName,
@ConfigName("collection.note") collectionNote: CollName,
@ConfigName("collection.trophy") collectionTrophy: CollName,
@ConfigName("collection.trophyKind") collectionTrophyKind: CollName,
@ConfigName("collection.ranking") collectionRanking: CollName,
@ConfigName("password.bpass.secret") passwordBPassSecret: Secret
private class UserConfig(
@ConfigName("cached.nb.ttl") val cachedNbTtl: FiniteDuration,
@ConfigName("online.ttl") val onlineTtl: FiniteDuration,
@ConfigName("collection.user") val collectionUser: CollName,
@ConfigName("collection.note") val collectionNote: CollName,
@ConfigName("collection.trophy") val collectionTrophy: CollName,
@ConfigName("collection.trophyKind") val collectionTrophyKind: CollName,
@ConfigName("collection.ranking") val collectionRanking: CollName,
@ConfigName("password.bpass.secret") val passwordBPassSecret: Secret
)
final class Env(

View File

@ -27,7 +27,10 @@ object BuildSettings {
.setPreference(DanglingCloseParenthesis, Force)
.setPreference(DoubleIndentConstructorArguments, true)
def defaultDeps = Seq(scalaz, chess, scalalib, jodaTime, ws, macwire, autoconfig) // , specs2, specs2Scalaz)
def defaultDeps = Seq(
scalaz, chess, scalalib, jodaTime, ws,
macwire.macros, macwire.util, autoconfig
) // , specs2, specs2Scalaz)
def compile(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "compile")
def provided(deps: ModuleID*): Seq[ModuleID] = deps map (_ % "provided")

View File

@ -40,11 +40,16 @@ object Dependencies {
val lettuce = "io.lettuce" % "lettuce-core" % "5.2.1.RELEASE"
val epoll = "io.netty" % "netty-transport-native-epoll" % "4.1.43.Final" classifier "linux-x86_64"
val markdown = "com.vladsch.flexmark" % "flexmark-all" % "0.50.44"
val macwire = "com.softwaremill.macwire" %% "macros" % "2.3.3" % "provided"
val autoconfig = "io.methvin.play" %% "autoconfig-macros" % "0.3.0"
object macwire {
val version = "2.3.3"
val macros = "com.softwaremill.macwire" %% "macros" % version
val util = "com.softwaremill.macwire" %% "util" % version
}
object reactivemongo {
val version = "0.19.1"
val version = "0.19.2"
val driver = "org.reactivemongo" %% "reactivemongo" % version
val bson = "org.reactivemongo" %% "reactivemongo-bson-api" % version
val native = "org.reactivemongo" % "reactivemongo-shaded-native" % s"$version-linux-x86-64" % "runtime" classifier "linux-x86_64"