migration WIP
parent
08ae65fb81
commit
e0b39662c7
|
@ -136,7 +136,7 @@ lazy val search = module("search", Seq(common, hub)).settings(
|
|||
)
|
||||
|
||||
lazy val chat = module("chat", Seq(common, db, user, security, i18n, socket)).settings(
|
||||
libraryDependencies ++= provided(play.api, scalatags) ++ reactivemongo.bundle
|
||||
libraryDependencies ++= provided(play.api, play.joda, scalatags) ++ reactivemongo.bundle
|
||||
)
|
||||
|
||||
lazy val room = module("room", Seq(common, socket, chat)).settings(
|
||||
|
|
|
@ -492,7 +492,7 @@ challenge {
|
|||
max_playing = ${setup.max_playing}
|
||||
}
|
||||
evalCache {
|
||||
collection.eval_cache = eval_cache
|
||||
collection.evalCache = eval_cache
|
||||
}
|
||||
irwin {
|
||||
collection.report = irwin_report
|
||||
|
|
|
@ -87,6 +87,8 @@ object Chat {
|
|||
|
||||
case class Setup(id: Id, publicSource: PublicSource)
|
||||
|
||||
case class MaxLines(value: Int) extends AnyVal with IntValue
|
||||
|
||||
def tournamentSetup(tourId: String) = Setup(Id(tourId), PublicSource.Tournament(tourId))
|
||||
def simulSetup(simulId: String) = Setup(Id(simulId), PublicSource.Simul(simulId))
|
||||
|
||||
|
|
|
@ -5,22 +5,24 @@ import reactivemongo.api.ReadPreference
|
|||
import scala.concurrent.duration._
|
||||
|
||||
import lila.db.dsl._
|
||||
import lila.common.config.NetConfig
|
||||
import lila.hub.actorApi.shutup.{ PublicSource, RecordPublicChat, RecordPrivateChat }
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
final class ChatApi(
|
||||
coll: Coll,
|
||||
userRepo: UserRepo,
|
||||
chatTimeout: ChatTimeout,
|
||||
flood: lila.security.Flood,
|
||||
spam: lila.security.Spam,
|
||||
shutup: akka.actor.ActorSelection,
|
||||
modLog: akka.actor.ActorSelection,
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
maxLinesPerChat: Int,
|
||||
netDomain: String
|
||||
maxLinesPerChat: Chat.MaxLines,
|
||||
net: NetConfig
|
||||
) {
|
||||
|
||||
import Chat.{ userChatBSONHandler, chatIdBSONHandler, classify }
|
||||
import Chat.{ userChatBSONHandler, chatIdBSONHandler, chanOf }
|
||||
|
||||
object userChat {
|
||||
|
||||
|
@ -87,7 +89,7 @@ final class ChatApi(
|
|||
}
|
||||
}
|
||||
|
||||
def clear(chatId: Chat.Id) = coll.remove($id(chatId)).void
|
||||
def clear(chatId: Chat.Id) = coll.delete.one($id(chatId)).void
|
||||
|
||||
def system(chatId: Chat.Id, text: String): Funit = {
|
||||
val line = UserLine(systemUserId, None, text, troll = false, deleted = false)
|
||||
|
@ -102,13 +104,13 @@ final class ChatApi(
|
|||
}
|
||||
|
||||
def timeout(chatId: Chat.Id, modId: User.ID, userId: User.ID, reason: ChatTimeout.Reason, local: Boolean): Funit =
|
||||
coll.byId[UserChat](chatId.value) zip UserRepo.byId(modId) zip UserRepo.byId(userId) flatMap {
|
||||
coll.byId[UserChat](chatId.value) zip userRepo.byId(modId) zip userRepo.byId(userId) flatMap {
|
||||
case Some(chat) ~ Some(mod) ~ Some(user) if isMod(mod) || local => doTimeout(chat, mod, user, reason)
|
||||
case _ => fuccess(none)
|
||||
}
|
||||
|
||||
def userModInfo(username: String): Fu[Option[UserModInfo]] =
|
||||
UserRepo named username flatMap {
|
||||
userRepo named username flatMap {
|
||||
_ ?? { user =>
|
||||
chatTimeout.history(user, 20) dmap { UserModInfo(user, _).some }
|
||||
}
|
||||
|
@ -123,7 +125,7 @@ final class ChatApi(
|
|||
)
|
||||
val c2 = c.markDeleted(user)
|
||||
val chat = line.fold(c2)(c2.add)
|
||||
coll.update($id(chat.id), chat).void >>
|
||||
coll.update.one($id(chat.id), chat).void >>
|
||||
chatTimeout.add(c, mod, user, reason) >>- {
|
||||
cached invalidate chat.id
|
||||
publish(chat.id, actorApi.OnTimeout(user.id))
|
||||
|
@ -137,7 +139,7 @@ final class ChatApi(
|
|||
|
||||
def delete(c: UserChat, user: User): Funit = {
|
||||
val chat = c.markDeleted(user)
|
||||
coll.update($id(chat.id), chat).void >>- {
|
||||
coll.update.one($id(chat.id), chat).void >>- {
|
||||
cached invalidate chat.id
|
||||
publish(chat.id, actorApi.OnTimeout(user.id))
|
||||
}
|
||||
|
@ -150,7 +152,7 @@ final class ChatApi(
|
|||
}
|
||||
|
||||
private[ChatApi] def makeLine(chatId: Chat.Id, userId: String, t1: String): Fu[Option[UserLine]] =
|
||||
UserRepo.speaker(userId) zip chatTimeout.isActive(chatId, userId) dmap {
|
||||
userRepo.speaker(userId) zip chatTimeout.isActive(chatId, userId) dmap {
|
||||
case (Some(user), false) if user.enabled => Writer cut t1 flatMap { t2 =>
|
||||
(user.isBot || flood.allowMessage(userId, t2)) option {
|
||||
if (~user.troll) lila.mon.chat.trollTrue()
|
||||
|
@ -193,18 +195,18 @@ final class ChatApi(
|
|||
}
|
||||
|
||||
private def publish(chatId: Chat.Id, msg: Any): Unit =
|
||||
lila.common.Bus.publish(msg, classify(chatId))
|
||||
lila.common.Bus.publish(msg, chanOf(chatId))
|
||||
|
||||
def remove(chatId: Chat.Id) = coll.remove($id(chatId)).void
|
||||
def remove(chatId: Chat.Id) = coll.delete.one($id(chatId)).void
|
||||
|
||||
def removeAll(chatIds: List[Chat.Id]) = coll.remove($inIds(chatIds)).void
|
||||
def removeAll(chatIds: List[Chat.Id]) = coll.delete.one($inIds(chatIds)).void
|
||||
|
||||
private def pushLine(chatId: Chat.Id, line: Line): Funit = coll.update(
|
||||
private def pushLine(chatId: Chat.Id, line: Line): Funit = coll.update.one(
|
||||
$id(chatId),
|
||||
$doc("$push" -> $doc(
|
||||
Chat.BSONFields.lines -> $doc(
|
||||
"$each" -> List(line),
|
||||
"$slice" -> -maxLinesPerChat
|
||||
"$slice" -> -maxLinesPerChat.value
|
||||
)
|
||||
)),
|
||||
upsert = true
|
||||
|
@ -218,8 +220,8 @@ final class ChatApi(
|
|||
|
||||
def cut(text: String) = Some(text.trim take Line.textMaxSize) filter (_.nonEmpty)
|
||||
|
||||
private val gameUrlRegex = (Pattern.quote(netDomain) + """\b/(\w{8})\w{4}\b""").r
|
||||
private val gameUrlReplace = Matcher.quoteReplacement(netDomain) + "/$1";
|
||||
private val gameUrlRegex = (Pattern.quote(net.domain) + """\b/(\w{8})\w{4}\b""").r
|
||||
private val gameUrlReplace = Matcher.quoteReplacement(net.domain) + "/$1";
|
||||
private def noPrivateUrl(str: String): String = gameUrlRegex.replaceAllIn(str, gameUrlReplace)
|
||||
private def noShouting(str: String): String = if (isShouting(str)) str.toLowerCase else str
|
||||
private val multilineRegex = """\n\n{2,}+""".r
|
||||
|
|
|
@ -66,13 +66,13 @@ object ChatTimeout {
|
|||
case object Insult extends Reason("insult", "disrespecting other players")
|
||||
case object Spam extends Reason("spam", "spamming the chat")
|
||||
case object Other extends Reason("other", "inappropriate behavior")
|
||||
val all = List(PublicShaming, Insult, Spam, Other)
|
||||
val all: List[Reason] = List(PublicShaming, Insult, Spam, Other)
|
||||
def apply(key: String) = all.find(_.key == key)
|
||||
}
|
||||
implicit val ReasonBSONHandler: BSONHandler[BSONString, Reason] = new BSONHandler[BSONString, Reason] {
|
||||
def read(b: BSONString) = Reason(b.value) err s"Invalid reason ${b.value}"
|
||||
def write(x: Reason) = BSONString(x.key)
|
||||
}
|
||||
implicit val ReasonBSONHandler: BSONHandler[Reason] = lila.db.BSON.tryHandler[Reason](
|
||||
{ case BSONString(value) => Reason(value) toTry s"Invalid reason ${value}" },
|
||||
x => BSONString(x.key)
|
||||
)
|
||||
|
||||
case class Reinstate(_id: String, chat: String, user: String)
|
||||
implicit val ReinstateBSONReader: BSONDocumentReader[Reinstate] = Macros.reader[Reinstate]
|
||||
|
|
|
@ -1,67 +1,59 @@
|
|||
package lila.chat
|
||||
|
||||
import akka.actor.{ ActorSystem, Props, ActorSelection }
|
||||
import com.typesafe.config.Config
|
||||
import com.softwaremill.macwire._
|
||||
import io.methvin.play.autoconfig._
|
||||
import play.api.Configuration
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
import lila.common.config._
|
||||
|
||||
private case class ChatConfig(
|
||||
@ConfigName("collection.chat") chatColl: CollName,
|
||||
@ConfigName("collection.timeout") timeoutColl: CollName,
|
||||
@ConfigName("max_lines") maxLines: Chat.MaxLines,
|
||||
@ConfigName("actor.name") actorName: String,
|
||||
net: NetConfig,
|
||||
@ConfigName("timeout.duration") timeoutDuration: FiniteDuration,
|
||||
@ConfigName("timeout.check_every") timeoutCheckEvery: FiniteDuration
|
||||
)
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
appConfig: Configuration,
|
||||
userRepo: lila.user.UserRepo,
|
||||
db: lila.db.Env,
|
||||
flood: lila.security.Flood,
|
||||
spam: lila.security.Spam,
|
||||
shutup: ActorSelection,
|
||||
modLog: ActorSelection,
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
system: ActorSystem
|
||||
) {
|
||||
asyncCache: lila.memo.AsyncCache.Builder
|
||||
)(implicit system: ActorSystem) {
|
||||
|
||||
private val settings = new {
|
||||
val CollectionChat = config getString "collection.chat"
|
||||
val CollectionTimeout = config getString "collection.timeout"
|
||||
val MaxLinesPerChat = config getInt "max_lines"
|
||||
val NetDomain = config getString "net.domain"
|
||||
val ActorName = config getString "actor.name"
|
||||
val TimeoutDuration = config duration "timeout.duration"
|
||||
val TimeoutCheckEvery = config duration "timeout.check_every"
|
||||
}
|
||||
import settings._
|
||||
private implicit val maxPerLineLoader = intLoader(Chat.MaxLines.apply)
|
||||
private val config = appConfig.get[ChatConfig]("chat")(AutoConfig.loader)
|
||||
import config._
|
||||
|
||||
val timeout = new ChatTimeout(
|
||||
coll = timeoutColl,
|
||||
duration = TimeoutDuration
|
||||
coll = db(timeoutColl),
|
||||
duration = timeoutDuration
|
||||
)
|
||||
|
||||
val api = new ChatApi(
|
||||
coll = chatColl,
|
||||
coll = db(chatColl),
|
||||
userRepo = userRepo,
|
||||
chatTimeout = timeout,
|
||||
flood = flood,
|
||||
spam = spam,
|
||||
shutup = shutup,
|
||||
modLog = modLog,
|
||||
asyncCache = asyncCache,
|
||||
maxLinesPerChat = MaxLinesPerChat,
|
||||
netDomain = NetDomain
|
||||
maxLinesPerChat = maxLines,
|
||||
net = net
|
||||
)
|
||||
|
||||
val panic = new ChatPanic
|
||||
val panic = wire[ChatPanic]
|
||||
|
||||
system.scheduler.schedule(TimeoutCheckEvery, TimeoutCheckEvery) {
|
||||
timeout.checkExpired foreach api.userChat.reinstate
|
||||
system.scheduler.scheduleWithFixedDelay(timeoutCheckEvery, timeoutCheckEvery) {
|
||||
() => timeout.checkExpired foreach api.userChat.reinstate
|
||||
}
|
||||
|
||||
private[chat] lazy val chatColl = db(CollectionChat)
|
||||
private[chat] lazy val timeoutColl = db(CollectionTimeout)
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current: Env = "chat" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "chat",
|
||||
db = lila.db.Env.current,
|
||||
flood = lila.security.Env.current.flood,
|
||||
spam = lila.security.Env.current.spam,
|
||||
shutup = lila.hub.Env.current.shutup,
|
||||
modLog = lila.hub.Env.current.mod,
|
||||
asyncCache = lila.memo.Env.current.asyncCache,
|
||||
system = lila.common.PlayApp.system
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,9 +3,14 @@ package lila.chat
|
|||
import lila.common.LightUser
|
||||
import lila.common.PimpedJson._
|
||||
import play.api.libs.json._
|
||||
import play.api.libs.json.JodaWrites._
|
||||
|
||||
object JsonView {
|
||||
|
||||
import writers._
|
||||
|
||||
lazy val timeoutReasons = Json toJson ChatTimeout.Reason.all
|
||||
|
||||
def apply(chat: AnyChat): JsValue = chat match {
|
||||
case c: MixedChat => mixedChatWriter writes c
|
||||
case c: UserChat => userChatWriter writes c
|
||||
|
@ -23,46 +28,48 @@ object JsonView {
|
|||
"writeable" -> writeable
|
||||
)
|
||||
|
||||
implicit val chatIdWrites: Writes[Chat.Id] = stringIsoWriter(Chat.chatIdIso)
|
||||
object writers {
|
||||
|
||||
lazy val timeoutReasons = Json toJson ChatTimeout.Reason.all
|
||||
implicit val chatIdWrites: Writes[Chat.Id] = stringIsoWriter(Chat.chatIdIso)
|
||||
|
||||
implicit val timeoutReasonWriter: Writes[ChatTimeout.Reason] = OWrites[ChatTimeout.Reason] { r =>
|
||||
Json.obj("key" -> r.key, "name" -> r.name)
|
||||
}
|
||||
implicit val timeoutReasonWriter: Writes[ChatTimeout.Reason] = OWrites[ChatTimeout.Reason] { r =>
|
||||
Json.obj("key" -> r.key, "name" -> r.name)
|
||||
}
|
||||
|
||||
implicit def timeoutEntryWriter(implicit lightUser: LightUser.GetterSync) = OWrites[ChatTimeout.UserEntry] { e =>
|
||||
Json.obj(
|
||||
"reason" -> e.reason.key,
|
||||
"mod" -> lightUser(e.mod).fold("?")(_.name),
|
||||
"date" -> e.createdAt
|
||||
)
|
||||
}
|
||||
implicit def timeoutEntryWriter(implicit lightUser: LightUser.GetterSync): OWrites[ChatTimeout.UserEntry] =
|
||||
OWrites[ChatTimeout.UserEntry] { e =>
|
||||
Json.obj(
|
||||
"reason" -> e.reason.key,
|
||||
"mod" -> lightUser(e.mod).fold("?")(_.name),
|
||||
"date" -> e.createdAt
|
||||
)
|
||||
}
|
||||
|
||||
implicit val mixedChatWriter: Writes[MixedChat] = Writes[MixedChat] { c =>
|
||||
JsArray(c.lines map lineWriter.writes)
|
||||
}
|
||||
implicit val mixedChatWriter: Writes[MixedChat] = Writes[MixedChat] { c =>
|
||||
JsArray(c.lines map lineWriter.writes)
|
||||
}
|
||||
|
||||
implicit val userChatWriter: Writes[UserChat] = Writes[UserChat] { c =>
|
||||
JsArray(c.lines map userLineWriter.writes)
|
||||
}
|
||||
implicit val userChatWriter: Writes[UserChat] = Writes[UserChat] { c =>
|
||||
JsArray(c.lines map userLineWriter.writes)
|
||||
}
|
||||
|
||||
private[chat] implicit val lineWriter: OWrites[Line] = OWrites[Line] {
|
||||
case l: UserLine => userLineWriter writes l
|
||||
case l: PlayerLine => playerLineWriter writes l
|
||||
}
|
||||
private[chat] implicit val lineWriter: OWrites[Line] = OWrites[Line] {
|
||||
case l: UserLine => userLineWriter writes l
|
||||
case l: PlayerLine => playerLineWriter writes l
|
||||
}
|
||||
|
||||
private implicit val userLineWriter = OWrites[UserLine] { l =>
|
||||
Json.obj(
|
||||
"u" -> l.username,
|
||||
"t" -> l.text
|
||||
).add("r" -> l.troll).add("d" -> l.deleted).add("title" -> l.title)
|
||||
}
|
||||
private implicit val userLineWriter = OWrites[UserLine] { l =>
|
||||
Json.obj(
|
||||
"u" -> l.username,
|
||||
"t" -> l.text
|
||||
).add("r" -> l.troll).add("d" -> l.deleted).add("title" -> l.title)
|
||||
}
|
||||
|
||||
private implicit val playerLineWriter = OWrites[PlayerLine] { l =>
|
||||
Json.obj(
|
||||
"c" -> l.color.name,
|
||||
"t" -> l.text
|
||||
)
|
||||
private implicit val playerLineWriter = OWrites[PlayerLine] { l =>
|
||||
Json.obj(
|
||||
"c" -> l.color.name,
|
||||
"t" -> l.text
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,15 +46,15 @@ object Line {
|
|||
|
||||
private val invalidLine = UserLine("", None, "[invalid character]", troll = false, deleted = true)
|
||||
|
||||
private[chat] implicit val userLineBSONHandler = new BSONHandler[BSONString, UserLine] {
|
||||
def read(bsonStr: BSONString) = strToUserLine(bsonStr.value) getOrElse invalidLine
|
||||
def write(x: UserLine) = BSONString(userLineToStr(x))
|
||||
}
|
||||
private[chat] implicit val userLineBSONHandler = lila.db.BSON.quickHandler[UserLine](
|
||||
{ case BSONString(value) => strToUserLine(value) getOrElse invalidLine },
|
||||
x => BSONString(userLineToStr(x))
|
||||
)
|
||||
|
||||
private[chat] implicit val lineBSONHandler = new BSONHandler[BSONString, Line] {
|
||||
def read(bsonStr: BSONString) = strToLine(bsonStr.value) getOrElse invalidLine
|
||||
def write(x: Line) = BSONString(lineToStr(x))
|
||||
}
|
||||
private[chat] implicit val lineBSONHandler = lila.db.BSON.quickHandler[Line](
|
||||
{ case BSONString(value) => strToLine(value) getOrElse invalidLine },
|
||||
x => BSONString(lineToStr(x))
|
||||
)
|
||||
|
||||
private val UserLineRegex = """(?s)([\w-~]{2,}+)([ !?])(.++)""".r
|
||||
private def strToUserLine(str: String): Option[UserLine] = str match {
|
||||
|
|
|
@ -28,9 +28,11 @@ final class PimpedOption[A](private val self: Option[A]) extends AnyVal {
|
|||
|
||||
def toFailure[B](b: => B): scalaz.Validation[A, B] = o.toFailure(self)(b)
|
||||
|
||||
def toTry(err: => Exception): Try[A] =
|
||||
def toTryWith(err: => Exception): Try[A] =
|
||||
self.fold[Try[A]](scala.util.Failure(err))(scala.util.Success.apply)
|
||||
|
||||
def toTry(err: => String): Try[A] = toTryWith(lila.base.LilaException(err))
|
||||
|
||||
def err(message: => String): A = self.getOrElse(sys.error(message))
|
||||
|
||||
def ifNone(n: => Unit): Unit = if (self.isEmpty) n
|
||||
|
|
|
@ -27,6 +27,6 @@ object config {
|
|||
implicit val emailAddressLoader = strLoader(EmailAddress.apply)
|
||||
implicit val netLoader = AutoConfig.loader[NetConfig]
|
||||
|
||||
private def strLoader[A](f: String => A): ConfigLoader[A] = ConfigLoader(_.getString) map f
|
||||
private def intLoader[A](f: Int => A): ConfigLoader[A] = ConfigLoader(_.getInt) map f
|
||||
def strLoader[A](f: String => A): ConfigLoader[A] = ConfigLoader(_.getString) map f
|
||||
def intLoader[A](f: Int => A): ConfigLoader[A] = ConfigLoader(_.getInt) map f
|
||||
}
|
||||
|
|
|
@ -3,8 +3,8 @@ package lila.db
|
|||
import org.joda.time.DateTime
|
||||
import ornicar.scalalib.Zero
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
|
||||
import reactivemongo.api.bson.compat._
|
||||
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import dsl._
|
||||
|
@ -108,7 +108,7 @@ object BSON extends Handlers {
|
|||
def quickHandler[T](read: PartialFunction[BSONValue, T], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
|
||||
def readTry(bson: BSONValue) = read.andThen(Success(_)).applyOrElse(
|
||||
bson,
|
||||
(b: BSONValue) => Failure(TypeDoesNotMatchException("BSONBinary", b.getClass.getSimpleName))
|
||||
(b: BSONValue) => handlerBadType(b)
|
||||
)
|
||||
def writeTry(t: T) = Success(write(t))
|
||||
}
|
||||
|
@ -116,11 +116,17 @@ object BSON extends Handlers {
|
|||
def tryHandler[T](read: PartialFunction[BSONValue, Try[T]], write: T => BSONValue): BSONHandler[T] = new BSONHandler[T] {
|
||||
def readTry(bson: BSONValue) = read.applyOrElse(
|
||||
bson,
|
||||
(b: BSONValue) => Failure(TypeDoesNotMatchException("BSONBinary", b.getClass.getSimpleName))
|
||||
(b: BSONValue) => handlerBadType(b)
|
||||
)
|
||||
def writeTry(t: T) = Success(write(t))
|
||||
}
|
||||
|
||||
def handlerBadType[T](b: BSONValue): Try[T] =
|
||||
Failure(TypeDoesNotMatchException("BSONBinary", b.getClass.getSimpleName))
|
||||
|
||||
def handlerBadValue[T](msg: String): Try[T] =
|
||||
Failure(new IllegalArgumentException(msg))
|
||||
|
||||
final class Reader(val doc: Bdoc) {
|
||||
|
||||
val map = {
|
||||
|
|
|
@ -3,7 +3,6 @@ package lila.db
|
|||
import scala.util.{ Try, Success, Failure }
|
||||
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
|
||||
|
||||
case class ByteArray(value: Array[Byte]) {
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ package lila.db
|
|||
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.api.bson._
|
||||
import reactivemongo.api.bson.exceptions.TypeDoesNotMatchException
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package lila.evalCache
|
||||
|
||||
import reactivemongo.api.bson._
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
import scalaz.NonEmptyList
|
||||
|
||||
import chess.format.Uci
|
||||
|
@ -14,7 +15,7 @@ private object BSONHandlers {
|
|||
private implicit val TrustBSONHandler = doubleAnyValHandler[Trust](_.value, Trust.apply)
|
||||
private implicit val KnodesBSONHandler = intAnyValHandler[Knodes](_.value, Knodes.apply)
|
||||
|
||||
implicit val PvsHandler = new BSONHandler[BSONString, NonEmptyList[Pv]] {
|
||||
implicit val PvsHandler = new BSONHandler[NonEmptyList[Pv]] {
|
||||
private def scoreWrite(s: Score): String = s.value.fold(_.value.toString, m => s"#${m.value}")
|
||||
private def scoreRead(str: String): Option[Score] =
|
||||
if (str startsWith "#") str.drop(1).toIntOption map { m => Score mate Mate(m) }
|
||||
|
@ -25,36 +26,46 @@ private object BSONHandlers {
|
|||
private val scoreSeparator = ':'
|
||||
private val pvSeparator = '/'
|
||||
private val pvSeparatorStr = pvSeparator.toString
|
||||
def read(bs: BSONString): NonEmptyList[Pv] = bs.value.split(pvSeparator).toList.map { pvStr =>
|
||||
pvStr.split(scoreSeparator) match {
|
||||
case Array(score, moves) => Pv(
|
||||
scoreRead(score) err s"Invalid score $score",
|
||||
movesRead(moves) err s"Invalid moves $moves"
|
||||
)
|
||||
case x => sys error s"Invalid PV $pvStr: ${x.toList} (in ${bs.value})"
|
||||
def readTry(bs: BSONValue) = bs match {
|
||||
case BSONString(value) => Try {
|
||||
value.split(pvSeparator).toList.map { pvStr =>
|
||||
pvStr.split(scoreSeparator) match {
|
||||
case Array(score, moves) => Pv(
|
||||
scoreRead(score) err s"Invalid score $score",
|
||||
movesRead(moves) err s"Invalid moves $moves"
|
||||
)
|
||||
case x => sys error s"Invalid PV $pvStr: ${x.toList} (in ${value})"
|
||||
}
|
||||
}
|
||||
}.flatMap {
|
||||
_.toNel toTry lila.base.LilaException(s"Empty PVs ${value}")
|
||||
}
|
||||
}.toNel err s"Empty PVs ${bs.value}"
|
||||
def write(x: NonEmptyList[Pv]) = BSONString {
|
||||
case b => lila.db.BSON.handlerBadType[NonEmptyList[Pv]](b)
|
||||
}
|
||||
def writeTry(x: NonEmptyList[Pv]) = Success(BSONString {
|
||||
x.toList.map { pv =>
|
||||
s"${scoreWrite(pv.score)}$scoreSeparator${movesWrite(pv.moves)}"
|
||||
} mkString pvSeparatorStr
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
implicit val EntryIdHandler = new BSONHandler[BSONString, Id] {
|
||||
def read(bs: BSONString): Id = bs.value split ':' match {
|
||||
case Array(fen) => Id(chess.variant.Standard, SmallFen raw fen)
|
||||
case Array(variantId, fen) => Id(
|
||||
variantId.toIntOption flatMap chess.variant.Variant.apply err s"Invalid evalcache variant $variantId",
|
||||
SmallFen raw fen
|
||||
)
|
||||
case _ => sys error s"Invalid evalcache id ${bs.value}"
|
||||
}
|
||||
def write(x: Id) = BSONString {
|
||||
implicit val EntryIdHandler = lila.db.BSON.tryHandler[Id](
|
||||
{
|
||||
case BSONString(value) =>
|
||||
value split ':' match {
|
||||
case Array(fen) => Success(Id(chess.variant.Standard, SmallFen raw fen))
|
||||
case Array(variantId, fen) => Success(Id(
|
||||
variantId.toIntOption flatMap chess.variant.Variant.apply err s"Invalid evalcache variant $variantId",
|
||||
SmallFen raw fen
|
||||
))
|
||||
case _ => lila.db.BSON.handlerBadValue(s"Invalid evalcache id ${value}")
|
||||
}
|
||||
},
|
||||
x => BSONString {
|
||||
if (x.variant.standard || x.variant == chess.variant.FromPosition) x.smallFen.value
|
||||
else s"${x.variant.id}:${x.smallFen.value}"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
implicit val evalHandler = Macros.handler[Eval]
|
||||
implicit val entryHandler = Macros.handler[EvalCacheEntry]
|
||||
|
|
|
@ -1,38 +1,32 @@
|
|||
package lila.evalCache
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.softwaremill.macwire._
|
||||
import play.api.Configuration
|
||||
import play.api.libs.json.JsValue
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.hub.actorApi.socket.remote.{ TellSriIn, TellSriOut }
|
||||
import lila.common.Bus
|
||||
import lila.common.config.CollName
|
||||
import lila.hub.actorApi.socket.remote.{ TellSriIn, TellSriOut }
|
||||
import lila.socket.Socket.Sri
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
appConfig: Configuration,
|
||||
userRepo: lila.user.UserRepo,
|
||||
settingStore: lila.memo.SettingStore.Builder,
|
||||
db: lila.db.Env,
|
||||
asyncCache: lila.memo.AsyncCache.Builder
|
||||
) {
|
||||
|
||||
private val CollectionEvalCache = config getString "collection.eval_cache"
|
||||
private lazy val coll = db(appConfig.get[CollName]("evalCache.collection.evalCache"))
|
||||
|
||||
private lazy val truster = new EvalCacheTruster(asyncCache)
|
||||
private lazy val truster = wire[EvalCacheTruster]
|
||||
|
||||
private lazy val upgrade = new EvalCacheUpgrade
|
||||
private lazy val upgrade = wire[EvalCacheUpgrade]
|
||||
|
||||
lazy val api = new EvalCacheApi(
|
||||
coll = db(CollectionEvalCache),
|
||||
truster = truster,
|
||||
upgrade = upgrade,
|
||||
asyncCache = asyncCache
|
||||
)
|
||||
lazy val api: EvalCacheApi = wire[EvalCacheApi]
|
||||
|
||||
private lazy val socketHandler = new EvalCacheSocketHandler(
|
||||
api = api,
|
||||
truster = truster,
|
||||
upgrade = upgrade
|
||||
)
|
||||
private lazy val socketHandler = wire[EvalCacheSocketHandler]
|
||||
|
||||
// remote socket support
|
||||
Bus.subscribeFun("remoteSocketIn:evalGet") {
|
||||
|
@ -55,13 +49,3 @@ final class Env(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
||||
lazy val current: Env = "evalCache" boot new Env(
|
||||
config = lila.common.PlayApp loadConfig "evalCache",
|
||||
settingStore = lila.memo.Env.current.settingStore,
|
||||
db = lila.db.Env.current,
|
||||
asyncCache = lila.memo.Env.current.asyncCache
|
||||
)
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ final class EvalCacheApi(
|
|||
|
||||
private[evalCache] def drop(variant: Variant, fen: FEN): Funit = {
|
||||
val id = Id(chess.variant.Standard, SmallFen.make(variant, fen))
|
||||
coll.remove($id(id)).void >>- cache.put(id, none)
|
||||
coll.delete.one($id(id)).void >>- cache.put(id, none)
|
||||
}
|
||||
|
||||
private val cache = asyncCache.multi[Id, Option[EvalCacheEntry]](
|
||||
|
@ -58,7 +58,7 @@ final class EvalCacheApi(
|
|||
private def getEntry(id: Id): Fu[Option[EvalCacheEntry]] = cache get id
|
||||
|
||||
private def fetchAndSetAccess(id: Id): Fu[Option[EvalCacheEntry]] =
|
||||
coll.find($id(id)).one[EvalCacheEntry] addEffect { res =>
|
||||
coll.ext.find($id(id)).one[EvalCacheEntry] addEffect { res =>
|
||||
if (res.isDefined) coll.updateFieldUnchecked($id(id), "usedAt", DateTime.now)
|
||||
}
|
||||
|
||||
|
@ -74,13 +74,13 @@ final class EvalCacheApi(
|
|||
evals = List(input.eval),
|
||||
usedAt = DateTime.now
|
||||
)
|
||||
coll.insert(entry).recover(lila.db.recoverDuplicateKey(_ => ())) >>-
|
||||
coll.insert.one(entry).recover(lila.db.recoverDuplicateKey(_ => ())) >>-
|
||||
cache.put(input.id, entry.some) >>-
|
||||
upgrade.onEval(input, sri)
|
||||
case Some(oldEntry) =>
|
||||
val entry = oldEntry add input.eval
|
||||
!(entry similarTo oldEntry) ?? {
|
||||
coll.update($id(entry.id), entry, upsert = true).void >>-
|
||||
coll.update.one($id(entry.id), entry, upsert = true).void >>-
|
||||
cache.put(input.id, entry.some) >>-
|
||||
upgrade.onEval(input, sri)
|
||||
}
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package lila.evalCache
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import org.joda.time.{ DateTime, Days }
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.security.Granter
|
||||
import lila.user.{ User, UserRepo }
|
||||
|
||||
private final class EvalCacheTruster(asyncCache: lila.memo.AsyncCache.Builder) {
|
||||
private final class EvalCacheTruster(
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
userRepo: UserRepo
|
||||
) {
|
||||
|
||||
import EvalCacheEntry.{ Trust, TrustedUser }
|
||||
|
||||
|
@ -25,7 +28,7 @@ private final class EvalCacheTruster(asyncCache: lila.memo.AsyncCache.Builder) {
|
|||
|
||||
private val userIdCache = asyncCache.multi[User.ID, Option[TrustedUser]](
|
||||
name = "evalCache.userIdTrustCache ",
|
||||
f = userId => UserRepo named userId map2 makeTrusted,
|
||||
f = userId => userRepo named userId map2 makeTrusted,
|
||||
expireAfter = _.ExpireAfterWrite(10 minutes),
|
||||
resultTimeout = 10 seconds
|
||||
)
|
||||
|
|
|
@ -59,10 +59,7 @@ object OAuthScope {
|
|||
import reactivemongo.api.bson._
|
||||
import lila.db.dsl._
|
||||
private[oauth] implicit val scopeHandler = lila.db.BSON.tryHandler[OAuthScope](
|
||||
{
|
||||
case b: BSONString => OAuthScope.byKey.get(b.value) toTry
|
||||
lila.base.LilaException(s"No such scope: ${b.value}")
|
||||
},
|
||||
{ case b: BSONString => OAuthScope.byKey.get(b.value) toTry s"No such scope: ${b.value}" },
|
||||
s => BSONString(s.key)
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue