full remote sockets WIP
parent
bba93795fe
commit
ec0aad51ae
|
@ -80,7 +80,6 @@ final class Env(
|
||||||
user <- lila.user.UserRepo byId userId flatten s"No such user $userId"
|
user <- lila.user.UserRepo byId userId flatten s"No such user $userId"
|
||||||
goodUser <- !user.lameOrTroll ?? { !Env.playban.api.hasCurrentBan(user.id) }
|
goodUser <- !user.lameOrTroll ?? { !Env.playban.api.hasCurrentBan(user.id) }
|
||||||
_ <- lila.user.UserRepo.disable(user, keepEmail = !goodUser)
|
_ <- lila.user.UserRepo.disable(user, keepEmail = !goodUser)
|
||||||
_ = Env.user.onlineUserIdMemo.remove(user.id)
|
|
||||||
_ = Env.user.recentTitledUserIdMemo.remove(user.id)
|
_ = Env.user.recentTitledUserIdMemo.remove(user.id)
|
||||||
_ <- !goodUser ?? Env.relation.api.fetchFollowing(user.id) flatMap {
|
_ <- !goodUser ?? Env.relation.api.fetchFollowing(user.id) flatMap {
|
||||||
Env.activity.write.unfollowAll(user, _)
|
Env.activity.write.unfollowAll(user, _)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import play.api.http._
|
||||||
import play.api.mvc.Codec
|
import play.api.mvc.Codec
|
||||||
import scalatags.Text.Frag
|
import scalatags.Text.Frag
|
||||||
|
|
||||||
package object app extends PackageObject with socket.WithSocket {
|
package object app extends PackageObject {
|
||||||
|
|
||||||
implicit def contentTypeOfFrag(implicit codec: Codec): ContentTypeOf[Frag] =
|
implicit def contentTypeOfFrag(implicit codec: Codec): ContentTypeOf[Frag] =
|
||||||
ContentTypeOf[Frag](Some(ContentTypes.HTML))
|
ContentTypeOf[Frag](Some(ContentTypes.HTML))
|
||||||
|
|
|
@ -4,9 +4,6 @@ import akka.actor._
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.hub.actorApi.round.NbRounds
|
|
||||||
import lila.socket.actorApi.NbMembers
|
|
||||||
|
|
||||||
private final class KamonPusher(
|
private final class KamonPusher(
|
||||||
countUsers: () => Int
|
countUsers: () => Int
|
||||||
) extends Actor {
|
) extends Actor {
|
||||||
|
@ -25,12 +22,6 @@ private final class KamonPusher(
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
|
|
||||||
case NbMembers(nb) =>
|
|
||||||
lila.mon.socket.count.all(nb)
|
|
||||||
|
|
||||||
case NbRounds(nb) =>
|
|
||||||
lila.mon.round.actor.count(nb)
|
|
||||||
|
|
||||||
case Tick =>
|
case Tick =>
|
||||||
lila.mon.jvm.thread(threadStats.getThreadCount)
|
lila.mon.jvm.thread(threadStats.getThreadCount)
|
||||||
lila.mon.jvm.daemon(threadStats.getDaemonThreadCount)
|
lila.mon.jvm.daemon(threadStats.getDaemonThreadCount)
|
||||||
|
@ -45,5 +36,5 @@ object KamonPusher {
|
||||||
private case object Tick
|
private case object Tick
|
||||||
|
|
||||||
def start(system: ActorSystem)(instance: => Actor) =
|
def start(system: ActorSystem)(instance: => Actor) =
|
||||||
system.lilaBus.subscribe(system.actorOf(Props(instance)), 'nbMembers, 'nbRounds)
|
system.lilaBus.subscribe(system.actorOf(Props(instance)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import lila.round.actorApi.round.{ DrawNo, DrawYes }
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
final class BotPlayer(
|
final class BotPlayer(
|
||||||
chatActor: ActorSelection,
|
chatApi: lila.chat.ChatApi,
|
||||||
isOfferingRematch: Pov => Boolean
|
isOfferingRematch: Pov => Boolean
|
||||||
)(implicit system: ActorSystem) {
|
)(implicit system: ActorSystem) {
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@ final class BotPlayer(
|
||||||
val source = d.room == "spectator" option {
|
val source = d.room == "spectator" option {
|
||||||
lila.hub.actorApi.shutup.PublicSource.Watcher(gameId)
|
lila.hub.actorApi.shutup.PublicSource.Watcher(gameId)
|
||||||
}
|
}
|
||||||
chatActor ! lila.chat.actorApi.UserTalk(chatId, me.id, d.text, publicSource = source)
|
chatApi.userChat.write(chatId, me.id, d.text, publicSource = source)
|
||||||
}
|
}
|
||||||
|
|
||||||
def rematchAccept(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, true)
|
def rematchAccept(id: Game.ID, me: User): Fu[Boolean] = rematch(id, me, true)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import lila.game.{ Game, Pov }
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
hub: lila.hub.Env,
|
chatApi: lila.chat.ChatApi,
|
||||||
onlineUserIds: lila.memo.ExpireSetMemo,
|
onlineUserIds: lila.memo.ExpireSetMemo,
|
||||||
lightUserApi: lila.user.LightUserApi,
|
lightUserApi: lila.user.LightUserApi,
|
||||||
rematchOf: Game.ID => Option[Game.ID],
|
rematchOf: Game.ID => Option[Game.ID],
|
||||||
|
@ -17,7 +17,7 @@ final class Env(
|
||||||
|
|
||||||
lazy val gameStateStream = new GameStateStream(system, jsonView)
|
lazy val gameStateStream = new GameStateStream(system, jsonView)
|
||||||
|
|
||||||
lazy val player = new BotPlayer(hub.chat, isOfferingRematch)(system)
|
lazy val player = new BotPlayer(chatApi, isOfferingRematch)(system)
|
||||||
|
|
||||||
val form = BotForm
|
val form = BotForm
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,7 @@ object Env {
|
||||||
|
|
||||||
lazy val current: Env = "bot" boot new Env(
|
lazy val current: Env = "bot" boot new Env(
|
||||||
system = lila.common.PlayApp.system,
|
system = lila.common.PlayApp.system,
|
||||||
hub = lila.hub.Env.current,
|
chatApi = lila.chat.Env.current.api,
|
||||||
onlineUserIds = lila.user.Env.current.onlineUserIdMemo,
|
onlineUserIds = lila.user.Env.current.onlineUserIdMemo,
|
||||||
lightUserApi = lila.user.Env.current.lightUserApi,
|
lightUserApi = lila.user.Env.current.lightUserApi,
|
||||||
rematchOf = lila.game.Env.current.rematches.getIfPresent,
|
rematchOf = lila.game.Env.current.rematches.getIfPresent,
|
||||||
|
|
|
@ -2,9 +2,7 @@ package lila
|
||||||
|
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
import lila.socket.WithSocket
|
package object challenge extends PackageObject {
|
||||||
|
|
||||||
package object challenge extends PackageObject with WithSocket {
|
|
||||||
|
|
||||||
type EitherChallenger = Either[Challenge.Anonymous, Challenge.Registered]
|
type EitherChallenger = Either[Challenge.Anonymous, Challenge.Registered]
|
||||||
|
|
||||||
|
|
|
@ -196,9 +196,9 @@ final class ChatApi(
|
||||||
private def publish(chatId: Chat.Id, msg: Any): Unit =
|
private def publish(chatId: Chat.Id, msg: Any): Unit =
|
||||||
lilaBus.publish(msg, classify(chatId))
|
lilaBus.publish(msg, classify(chatId))
|
||||||
|
|
||||||
private[chat] def remove(chatId: Chat.Id) = coll.remove($id(chatId)).void
|
def remove(chatId: Chat.Id) = coll.remove($id(chatId)).void
|
||||||
|
|
||||||
private[chat] def removeAll(chatIds: List[Chat.Id]) = coll.remove($inIds(chatIds)).void
|
def removeAll(chatIds: List[Chat.Id]) = coll.remove($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(
|
||||||
$id(chatId),
|
$id(chatId),
|
||||||
|
|
|
@ -45,14 +45,10 @@ final class Env(
|
||||||
|
|
||||||
val panic = new ChatPanic
|
val panic = new ChatPanic
|
||||||
|
|
||||||
private val palantir = new Palantir(system.lilaBus)
|
|
||||||
|
|
||||||
system.scheduler.schedule(TimeoutCheckEvery, TimeoutCheckEvery) {
|
system.scheduler.schedule(TimeoutCheckEvery, TimeoutCheckEvery) {
|
||||||
timeout.checkExpired foreach api.userChat.reinstate
|
timeout.checkExpired foreach api.userChat.reinstate
|
||||||
}
|
}
|
||||||
|
|
||||||
system.actorOf(Props(new FrontActor(api, palantir)), name = ActorName)
|
|
||||||
|
|
||||||
private[chat] lazy val chatColl = db(CollectionChat)
|
private[chat] lazy val chatColl = db(CollectionChat)
|
||||||
private[chat] lazy val timeoutColl = db(CollectionTimeout)
|
private[chat] lazy val timeoutColl = db(CollectionTimeout)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package lila.chat
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import chess.Color
|
|
||||||
|
|
||||||
import actorApi._
|
|
||||||
|
|
||||||
private[chat] final class FrontActor(
|
|
||||||
api: ChatApi,
|
|
||||||
palantir: Palantir
|
|
||||||
) extends Actor {
|
|
||||||
|
|
||||||
def receive = {
|
|
||||||
|
|
||||||
case UserTalk(chatId, userId, text, source) => api.userChat.write(chatId, userId, text, source)
|
|
||||||
|
|
||||||
case PlayerTalk(chatId, color, text) => api.playerChat.write(chatId, Color(color), text)
|
|
||||||
|
|
||||||
case SystemTalk(chatId, text) => api.userChat.system(chatId, text)
|
|
||||||
|
|
||||||
case Timeout(chatId, modId, userId, reason, local) => api.userChat.timeout(chatId, modId, userId, reason, local)
|
|
||||||
|
|
||||||
case Remove(chatId) => api remove chatId
|
|
||||||
|
|
||||||
case RemoveAll(chatIds) => api removeAll chatIds
|
|
||||||
|
|
||||||
case Palantir.Ping(chatId, userId, sri) => palantir.ping(chatId, userId, sri)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package lila.chat
|
|
||||||
|
|
||||||
import com.github.blemale.scaffeine.{ Cache, Scaffeine }
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
import lila.socket.Socket.makeMessage
|
|
||||||
import lila.socket.SocketMember
|
|
||||||
import lila.user.User
|
|
||||||
|
|
||||||
private final class Palantir(bus: lila.common.Bus) {
|
|
||||||
|
|
||||||
import Palantir._
|
|
||||||
|
|
||||||
private val channels: Cache[Chat.Id, Channel] = Scaffeine()
|
|
||||||
.expireAfterWrite(1 minute)
|
|
||||||
.build[Chat.Id, Channel]
|
|
||||||
|
|
||||||
def ping(chatId: Chat.Id, userId: User.ID, member: SocketMember): Unit = {
|
|
||||||
val channel = channels.get(chatId, _ => new Channel)
|
|
||||||
channel.add(userId, member)
|
|
||||||
member.push(makeMessage("palantir", channel.userIds.filter(userId !=)))
|
|
||||||
lila.mon.palantir.channels(channels.estimatedSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def hangUp(userId: User.ID) =
|
|
||||||
channels.asMap.foreach {
|
|
||||||
case (_, channel) => channel get userId foreach {
|
|
||||||
_ push makeMessage("palantirOff")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bus.subscribeFun('shadowban, 'accountClose) {
|
|
||||||
case lila.hub.actorApi.mod.Shadowban(userId, true) => hangUp(userId)
|
|
||||||
case lila.hub.actorApi.security.CloseAccount(userId) => hangUp(userId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object Palantir {
|
|
||||||
|
|
||||||
class Channel {
|
|
||||||
|
|
||||||
private val members: Cache[User.ID, SocketMember] = Scaffeine()
|
|
||||||
.expireAfterWrite(7 seconds)
|
|
||||||
.build[User.ID, SocketMember]
|
|
||||||
|
|
||||||
def add(uid: User.ID, member: SocketMember) = members.put(uid, member)
|
|
||||||
|
|
||||||
def get(uid: User.ID) = members getIfPresent uid
|
|
||||||
|
|
||||||
def userIds = members.asMap.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Ping(chatId: Chat.Id, userId: User.ID, member: SocketMember)
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
package lila.chat
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
import lila.hub.actorApi.shutup.PublicSource
|
|
||||||
import lila.socket.{ Handler, SocketMember }
|
|
||||||
import lila.user.User
|
|
||||||
|
|
||||||
object Socket {
|
|
||||||
|
|
||||||
def in(
|
|
||||||
chatId: Chat.Id,
|
|
||||||
member: SocketMember,
|
|
||||||
chat: ActorSelection,
|
|
||||||
publicSource: Option[PublicSource],
|
|
||||||
canTimeout: Option[User.ID => Fu[Boolean]] = None
|
|
||||||
): Handler.Controller = {
|
|
||||||
|
|
||||||
case ("talk", o) => for {
|
|
||||||
text <- o str "d"
|
|
||||||
userId <- member.userId
|
|
||||||
} chat ! actorApi.UserTalk(chatId, userId, text, publicSource)
|
|
||||||
|
|
||||||
case ("timeout", o) => for {
|
|
||||||
data ← o obj "d"
|
|
||||||
modId <- member.userId
|
|
||||||
userId <- data.str("userId") map lila.user.User.normalize
|
|
||||||
reason <- data.str("reason") flatMap ChatTimeout.Reason.apply
|
|
||||||
} canTimeout.??(_(userId)) foreach { localTimeout =>
|
|
||||||
chat ! actorApi.Timeout(chatId, modId, userId, reason, local = localTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
case ("palantirPing", o) =>
|
|
||||||
member.userId foreach { userId =>
|
|
||||||
chat ! Palantir.Ping(chatId, userId, member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Send = (String, JsValue, Boolean) => Unit
|
|
||||||
|
|
||||||
def out(send: Send): Actor.Receive = {
|
|
||||||
|
|
||||||
case actorApi.ChatLine(_, line) => line match {
|
|
||||||
case line: UserLine => send("message", JsonView(line), line.troll)
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
|
|
||||||
case actorApi.OnTimeout(userId) => send("chat_timeout", JsString(userId), false)
|
|
||||||
|
|
||||||
case actorApi.OnReinstate(userId) => send("chat_reinstate", JsString(userId), false)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,8 @@
|
||||||
package lila.chat
|
package lila.chat
|
||||||
package actorApi
|
package actorApi
|
||||||
|
|
||||||
import lila.hub.actorApi.shutup.PublicSource
|
|
||||||
|
|
||||||
case class UserTalk(chatId: Chat.Id, userId: String, text: String, publicSource: Option[PublicSource])
|
|
||||||
case class PlayerTalk(chatId: Chat.Id, white: Boolean, text: String)
|
|
||||||
case class SystemTalk(chatId: Chat.Id, text: String)
|
|
||||||
case class ChatLine(chatId: Chat.Id, line: Line)
|
case class ChatLine(chatId: Chat.Id, line: Line)
|
||||||
case class Timeout(chatId: Chat.Id, mod: String, userId: String, reason: ChatTimeout.Reason, local: Boolean)
|
case class Timeout(chatId: Chat.Id, mod: String, userId: String, reason: ChatTimeout.Reason, local: Boolean)
|
||||||
|
|
||||||
case class OnTimeout(userId: String)
|
case class OnTimeout(userId: String)
|
||||||
case class OnReinstate(userId: String)
|
case class OnReinstate(userId: String)
|
||||||
case class Remove(chatId: Chat.Id)
|
|
||||||
case class RemoveAll(chatIds: List[Chat.Id])
|
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
package lila.common
|
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For small code blocks that don't need to be run on a separate thread.
|
|
||||||
*/
|
|
||||||
object SameThread extends ExecutionContext {
|
|
||||||
|
|
||||||
val logger = lila.log.sameThread
|
|
||||||
|
|
||||||
override def execute(runnable: Runnable): Unit = runnable.run
|
|
||||||
override def reportFailure(t: Throwable): Unit = logger.error(t.getMessage, t)
|
|
||||||
}
|
|
|
@ -327,12 +327,6 @@ object mon {
|
||||||
}
|
}
|
||||||
val deadMsg = inc("socket.dead.msg")
|
val deadMsg = inc("socket.dead.msg")
|
||||||
def queueSize(name: String) = rec(s"socket.queue_size.$name")
|
def queueSize(name: String) = rec(s"socket.queue_size.$name")
|
||||||
object remote {
|
|
||||||
object sets {
|
|
||||||
val users = rec("socket.remote.sets.users")
|
|
||||||
val games = rec("socket.remote.sets.games")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
object trouper {
|
object trouper {
|
||||||
def queueSize(name: String) = rec(s"trouper.queue_size.$name")
|
def queueSize(name: String) = rec(s"trouper.queue_size.$name")
|
||||||
|
|
|
@ -28,16 +28,12 @@ final class Env(
|
||||||
asyncCache = asyncCache
|
asyncCache = asyncCache
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val socketHandler = new EvalCacheSocketHandler(
|
private lazy val socketHandler = new EvalCacheSocketHandler(
|
||||||
api = api,
|
api = api,
|
||||||
truster = truster,
|
truster = truster,
|
||||||
upgrade = upgrade
|
upgrade = upgrade
|
||||||
)
|
)
|
||||||
|
|
||||||
bus.subscribeFun('socketLeave) {
|
|
||||||
case lila.socket.actorApi.SocketLeave(sri, _) => upgrade unregister sri
|
|
||||||
}
|
|
||||||
|
|
||||||
// remote socket support
|
// remote socket support
|
||||||
bus.subscribeFun(Symbol("remoteSocketIn:evalGet")) {
|
bus.subscribeFun(Symbol("remoteSocketIn:evalGet")) {
|
||||||
case TellSriIn(sri, _, msg) => msg obj "d" foreach { d =>
|
case TellSriIn(sri, _, msg) => msg obj "d" foreach { d =>
|
||||||
|
|
|
@ -7,7 +7,7 @@ import chess.format.FEN
|
||||||
import lila.socket._
|
import lila.socket._
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
final class EvalCacheSocketHandler(
|
private final class EvalCacheSocketHandler(
|
||||||
api: EvalCacheApi,
|
api: EvalCacheApi,
|
||||||
truster: EvalCacheTruster,
|
truster: EvalCacheTruster,
|
||||||
upgrade: EvalCacheUpgrade
|
upgrade: EvalCacheUpgrade
|
||||||
|
@ -15,23 +15,7 @@ final class EvalCacheSocketHandler(
|
||||||
|
|
||||||
import EvalCacheEntry._
|
import EvalCacheEntry._
|
||||||
|
|
||||||
def apply(sri: Socket.Sri, member: SocketMember, user: Option[User]): Handler.Controller =
|
def evalGet(
|
||||||
makeController(sri, member, user map truster.makeTrusted)
|
|
||||||
|
|
||||||
private def makeController(
|
|
||||||
sri: Socket.Sri,
|
|
||||||
member: SocketMember,
|
|
||||||
trustedUser: Option[TrustedUser]
|
|
||||||
): Handler.Controller = {
|
|
||||||
|
|
||||||
case ("evalPut", o) => trustedUser foreach { tu =>
|
|
||||||
JsonHandlers.readPut(tu, o) foreach { api.put(tu, _, sri) }
|
|
||||||
}
|
|
||||||
|
|
||||||
case ("evalGet", o) => o obj "d" foreach { evalGet(sri, _, member.push) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private[evalCache] def evalGet(
|
|
||||||
sri: Socket.Sri,
|
sri: Socket.Sri,
|
||||||
d: JsObject,
|
d: JsObject,
|
||||||
push: JsObject => Unit
|
push: JsObject => Unit
|
||||||
|
@ -50,7 +34,7 @@ final class EvalCacheSocketHandler(
|
||||||
if (d.value contains "up") upgrade.register(sri, variant, fen, multiPv, path)(pushData)
|
if (d.value contains "up") upgrade.register(sri, variant, fen, multiPv, path)(pushData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private[evalCache] def untrustedEvalPut(sri: Socket.Sri, userId: User.ID, data: JsObject): Unit =
|
def untrustedEvalPut(sri: Socket.Sri, userId: User.ID, data: JsObject): Unit =
|
||||||
truster cachedTrusted userId foreach {
|
truster cachedTrusted userId foreach {
|
||||||
_ foreach { tu =>
|
_ foreach { tu =>
|
||||||
JsonHandlers.readPutData(tu, data) foreach {
|
JsonHandlers.readPutData(tu, data) foreach {
|
||||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
||||||
|
|
||||||
import chess.format.FEN
|
import chess.format.FEN
|
||||||
import chess.variant.Variant
|
import chess.variant.Variant
|
||||||
import lila.socket.{ Socket, SocketMember }
|
import lila.socket.Socket
|
||||||
import lila.memo.ExpireCallbackMemo
|
import lila.memo.ExpireCallbackMemo
|
||||||
|
|
||||||
/* Upgrades the user's eval when a better one becomes available,
|
/* Upgrades the user's eval when a better one becomes available,
|
||||||
|
|
|
@ -18,13 +18,10 @@ final class Env(config: Config, system: ActorSystem) {
|
||||||
val report = select("actor.report")
|
val report = select("actor.report")
|
||||||
val shutup = select("actor.shutup")
|
val shutup = select("actor.shutup")
|
||||||
val mod = select("actor.mod")
|
val mod = select("actor.mod")
|
||||||
val chat = select("actor.chat")
|
|
||||||
val notification = select("actor.notify")
|
val notification = select("actor.notify")
|
||||||
|
|
||||||
val bus = system.lilaBus
|
|
||||||
|
|
||||||
private def select(name: String) =
|
private def select(name: String) =
|
||||||
system actorSelection ("/user/" + config.getString(name))
|
system.actorSelection("/user/" + config.getString(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
object Env {
|
object Env {
|
||||||
|
|
|
@ -27,8 +27,6 @@ package map {
|
||||||
}
|
}
|
||||||
|
|
||||||
package socket {
|
package socket {
|
||||||
case class WithUserIds(f: Iterable[String] => Unit)
|
|
||||||
case class HasUserId(userId: String, promise: Promise[Boolean])
|
|
||||||
case class SendTo(userId: String, message: JsObject)
|
case class SendTo(userId: String, message: JsObject)
|
||||||
object SendTo {
|
object SendTo {
|
||||||
def apply[A: Writes](userId: String, typ: String, data: A): SendTo =
|
def apply[A: Writes](userId: String, typ: String, data: A): SendTo =
|
||||||
|
@ -242,7 +240,6 @@ package round {
|
||||||
simulId: String,
|
simulId: String,
|
||||||
opponentUserId: String
|
opponentUserId: String
|
||||||
)
|
)
|
||||||
case class NbRounds(nb: Int)
|
|
||||||
case class Berserk(gameId: String, userId: String)
|
case class Berserk(gameId: String, userId: String)
|
||||||
case class IsOnGame(color: chess.Color, promise: Promise[Boolean])
|
case class IsOnGame(color: chess.Color, promise: Promise[Boolean])
|
||||||
case class TourStandingOld(data: JsArray)
|
case class TourStandingOld(data: JsArray)
|
||||||
|
|
|
@ -27,6 +27,7 @@ final class LobbySocket(
|
||||||
|
|
||||||
import LobbySocket._
|
import LobbySocket._
|
||||||
import Protocol._
|
import Protocol._
|
||||||
|
type SocketController = PartialFunction[(String, JsObject), Unit]
|
||||||
|
|
||||||
val trouper: Trouper = new Trouper {
|
val trouper: Trouper = new Trouper {
|
||||||
|
|
||||||
|
@ -142,7 +143,7 @@ final class LobbySocket(
|
||||||
private def HookPoolLimit[A: Zero](member: Member, cost: Int, msg: => String)(op: => A) =
|
private def HookPoolLimit[A: Zero](member: Member, cost: Int, msg: => String)(op: => A) =
|
||||||
poolLimitPerSri(k = member.sri.value, cost = cost, msg = msg)(op)
|
poolLimitPerSri(k = member.sri.value, cost = cost, msg = msg)(op)
|
||||||
|
|
||||||
def controller(member: Member): lila.socket.Handler.Controller = {
|
def controller(member: Member): SocketController = {
|
||||||
case ("join", o) if !member.bot => HookPoolLimit(member, cost = 5, msg = s"join $o") {
|
case ("join", o) if !member.bot => HookPoolLimit(member, cost = 5, msg = s"join $o") {
|
||||||
o str "d" foreach { id =>
|
o str "d" foreach { id =>
|
||||||
lobby ! BiteHook(id, member.sri, member.user)
|
lobby ! BiteHook(id, member.sri, member.user)
|
||||||
|
@ -231,7 +232,7 @@ final class LobbySocket(
|
||||||
getOrConnect(sri, user) foreach { member =>
|
getOrConnect(sri, user) foreach { member =>
|
||||||
controller(member).applyOrElse(tpe -> msg, {
|
controller(member).applyOrElse(tpe -> msg, {
|
||||||
case _ => logger.warn(s"Can't handle $tpe")
|
case _ => logger.warn(s"Can't handle $tpe")
|
||||||
}: lila.socket.Handler.Controller)
|
}: SocketController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,11 +242,6 @@ final class LobbySocket(
|
||||||
remoteSocketApi.subscribe("lobby-in", P.In.baseReader)(handler orElse remoteSocketApi.baseHandler)
|
remoteSocketApi.subscribe("lobby-in", P.In.baseReader)(handler orElse remoteSocketApi.baseHandler)
|
||||||
|
|
||||||
private val send: String => Unit = remoteSocketApi.makeSender("lobby-out").apply _
|
private val send: String => Unit = remoteSocketApi.makeSender("lobby-out").apply _
|
||||||
|
|
||||||
system.lilaBus.subscribeFun('nbMembers, 'nbRounds) {
|
|
||||||
case lila.socket.actorApi.NbMembers(nb) => send(Out.nbMembers(nb))
|
|
||||||
case lila.hub.actorApi.round.NbRounds(nb) => send(Out.nbRounds(nb))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private object LobbySocket {
|
private object LobbySocket {
|
||||||
|
@ -260,8 +256,6 @@ private object LobbySocket {
|
||||||
|
|
||||||
object Protocol {
|
object Protocol {
|
||||||
object Out {
|
object Out {
|
||||||
def nbMembers(nb: Int) = s"member/nb $nb"
|
|
||||||
def nbRounds(nb: Int) = s"round/nb $nb"
|
|
||||||
def pairings(pairings: List[PoolApi.Pairing]) = {
|
def pairings(pairings: List[PoolApi.Pairing]) = {
|
||||||
val redirs = for {
|
val redirs = for {
|
||||||
pairing <- pairings
|
pairing <- pairings
|
||||||
|
|
|
@ -6,7 +6,6 @@ import scala.concurrent.Promise
|
||||||
|
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.socket.Socket.{ Sri, Sris }
|
import lila.socket.Socket.{ Sri, Sris }
|
||||||
import lila.socket.{ SocketMember, DirectSocketMember, RemoteSocketMember }
|
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
private[lobby] case class SaveSeek(msg: AddSeek)
|
private[lobby] case class SaveSeek(msg: AddSeek)
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
import lila.socket.WithSocket
|
package object lobby extends PackageObject {
|
||||||
|
|
||||||
package object lobby extends PackageObject with WithSocket {
|
|
||||||
|
|
||||||
private[lobby] def logger = lila.log("lobby")
|
private[lobby] def logger = lila.log("lobby")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package lila.room
|
package lila.room
|
||||||
|
|
||||||
import lila.chat.{ Chat, UserLine, actorApi => chatApi }
|
import lila.chat.{ Chat, ChatApi, UserLine, actorApi => chatApi }
|
||||||
import lila.common.Bus
|
import lila.common.Bus
|
||||||
import lila.hub.actorApi.shutup.PublicSource
|
import lila.hub.actorApi.shutup.PublicSource
|
||||||
import lila.hub.{ Trouper, TrouperMap }
|
import lila.hub.{ Trouper, TrouperMap }
|
||||||
|
@ -62,16 +62,16 @@ object RoomSocket {
|
||||||
|
|
||||||
def roomHandler(
|
def roomHandler(
|
||||||
rooms: TrouperMap[RoomState],
|
rooms: TrouperMap[RoomState],
|
||||||
chat: akka.actor.ActorSelection,
|
chat: ChatApi,
|
||||||
logger: Logger,
|
logger: Logger,
|
||||||
publicSource: RoomId => PublicSource.type => Option[PublicSource],
|
publicSource: RoomId => PublicSource.type => Option[PublicSource],
|
||||||
localTimeout: Option[(RoomId, User.ID, User.ID) => Fu[Boolean]] = None
|
localTimeout: Option[(RoomId, User.ID, User.ID) => Fu[Boolean]] = None
|
||||||
): Handler = ({
|
): Handler = ({
|
||||||
case Protocol.In.ChatSay(roomId, userId, msg) =>
|
case Protocol.In.ChatSay(roomId, userId, msg) =>
|
||||||
chat ! lila.chat.actorApi.UserTalk(Chat.Id(roomId.value), userId, msg, publicSource(roomId)(PublicSource))
|
chat.userChat.write(Chat.Id(roomId.value), userId, msg, publicSource(roomId)(PublicSource))
|
||||||
case Protocol.In.ChatTimeout(roomId, modId, suspect, reason) => lila.chat.ChatTimeout.Reason(reason) foreach { r =>
|
case Protocol.In.ChatTimeout(roomId, modId, suspect, reason) => lila.chat.ChatTimeout.Reason(reason) foreach { r =>
|
||||||
localTimeout.?? { _(roomId, modId, suspect) } foreach { local =>
|
localTimeout.?? { _(roomId, modId, suspect) } foreach { local =>
|
||||||
chat ! lila.chat.actorApi.Timeout(Chat.Id(roomId.value), modId, suspect, r, local = local)
|
chat.userChat.timeout(Chat.Id(roomId.value), modId, suspect, r, local = local)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}: Handler) orElse minRoomHandler(rooms, logger)
|
}: Handler) orElse minRoomHandler(rooms, logger)
|
||||||
|
|
|
@ -11,8 +11,6 @@ import actorApi.{ GetSocketStatus, SocketStatus }
|
||||||
import lila.game.{ Game, GameRepo, Pov, PlayerRef }
|
import lila.game.{ Game, GameRepo, Pov, PlayerRef }
|
||||||
import lila.hub.actorApi.map.Tell
|
import lila.hub.actorApi.map.Tell
|
||||||
import lila.hub.actorApi.round.{ Abort, Resign, FishnetPlay }
|
import lila.hub.actorApi.round.{ Abort, Resign, FishnetPlay }
|
||||||
import lila.hub.actorApi.socket.HasUserId
|
|
||||||
import lila.hub.actorApi.{ Announce, DeployPost }
|
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
|
@ -20,6 +18,7 @@ final class Env(
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
db: lila.db.Env,
|
db: lila.db.Env,
|
||||||
hub: lila.hub.Env,
|
hub: lila.hub.Env,
|
||||||
|
chatApi: lila.chat.ChatApi,
|
||||||
fishnetPlayer: lila.fishnet.Player,
|
fishnetPlayer: lila.fishnet.Player,
|
||||||
aiPerfApi: lila.fishnet.AiPerfApi,
|
aiPerfApi: lila.fishnet.AiPerfApi,
|
||||||
crosstableApi: lila.game.CrosstableApi,
|
crosstableApi: lila.game.CrosstableApi,
|
||||||
|
@ -35,12 +34,10 @@ final class Env(
|
||||||
prefApi: lila.pref.PrefApi,
|
prefApi: lila.pref.PrefApi,
|
||||||
historyApi: lila.history.HistoryApi,
|
historyApi: lila.history.HistoryApi,
|
||||||
evalCache: lila.evalCache.EvalCacheApi,
|
evalCache: lila.evalCache.EvalCacheApi,
|
||||||
evalCacheHandler: lila.evalCache.EvalCacheSocketHandler,
|
|
||||||
remoteSocketApi: lila.socket.RemoteSocket,
|
remoteSocketApi: lila.socket.RemoteSocket,
|
||||||
isBotSync: lila.common.LightUser.IsBotSync,
|
isBotSync: lila.common.LightUser.IsBotSync,
|
||||||
slackApi: lila.slack.SlackApi,
|
slackApi: lila.slack.SlackApi,
|
||||||
ratingFactors: () => lila.rating.RatingFactors,
|
ratingFactors: () => lila.rating.RatingFactors
|
||||||
settingStore: lila.memo.SettingStore.Builder
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val settings = new {
|
private val settings = new {
|
||||||
|
@ -59,9 +56,6 @@ final class Env(
|
||||||
|
|
||||||
private val bus = system.lilaBus
|
private val bus = system.lilaBus
|
||||||
|
|
||||||
private val moveTimeChannel = new lila.socket.Channel(system)
|
|
||||||
bus.subscribe(moveTimeChannel, 'roundMoveTimeChannel)
|
|
||||||
|
|
||||||
private val deployPersistence = new DeployPersistence(system)
|
private val deployPersistence = new DeployPersistence(system)
|
||||||
|
|
||||||
private val defaultGoneWeight = fuccess(1f)
|
private val defaultGoneWeight = fuccess(1f)
|
||||||
|
@ -115,10 +109,6 @@ final class Env(
|
||||||
private var nbRounds = 0
|
private var nbRounds = 0
|
||||||
def count = nbRounds
|
def count = nbRounds
|
||||||
|
|
||||||
system.scheduler.schedule(5 seconds, 2 seconds) {
|
|
||||||
bus.publish(lila.hub.actorApi.round.NbRounds(roundSocket.rounds.size), 'nbRounds)
|
|
||||||
}
|
|
||||||
|
|
||||||
def tellRound(gameId: Game.ID, msg: Any): Unit = roundSocket.rounds.tell(gameId, msg)
|
def tellRound(gameId: Game.ID, msg: Any): Unit = roundSocket.rounds.tell(gameId, msg)
|
||||||
|
|
||||||
object proxy {
|
object proxy {
|
||||||
|
@ -228,15 +218,13 @@ final class Env(
|
||||||
bus = bus
|
bus = bus
|
||||||
)
|
)
|
||||||
|
|
||||||
lazy val messenger = new Messenger(
|
lazy val messenger = new Messenger(chatApi)
|
||||||
chat = hub.chat
|
|
||||||
)
|
|
||||||
|
|
||||||
def getSocketStatus(game: Game): Fu[SocketStatus] =
|
def getSocketStatus(game: Game): Fu[SocketStatus] =
|
||||||
roundSocket.rounds.ask[SocketStatus](game.id)(GetSocketStatus)
|
roundSocket.rounds.ask[SocketStatus](game.id)(GetSocketStatus)
|
||||||
|
|
||||||
private def isUserPresent(game: Game, userId: lila.user.User.ID): Fu[Boolean] =
|
private def isUserPresent(game: Game, userId: lila.user.User.ID): Fu[Boolean] =
|
||||||
roundSocket.rounds.askIfPresentOrZero[Boolean](game.id)(HasUserId(userId, _))
|
roundSocket.rounds.askIfPresentOrZero[Boolean](game.id)(RoundDuct.HasUserId(userId, _))
|
||||||
|
|
||||||
lazy val jsonView = new JsonView(
|
lazy val jsonView = new JsonView(
|
||||||
noteApi = noteApi,
|
noteApi = noteApi,
|
||||||
|
@ -263,10 +251,10 @@ final class Env(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MoveMonitor.start(system, moveTimeChannel)
|
MoveMonitor.start(system)
|
||||||
|
|
||||||
system.actorOf(
|
system.actorOf(
|
||||||
Props(new Titivate(tellRound, hub.bookmark, hub.chat)),
|
Props(new Titivate(tellRound, hub.bookmark, chatApi)),
|
||||||
name = "titivate"
|
name = "titivate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -303,6 +291,7 @@ object Env {
|
||||||
system = lila.common.PlayApp.system,
|
system = lila.common.PlayApp.system,
|
||||||
db = lila.db.Env.current,
|
db = lila.db.Env.current,
|
||||||
hub = lila.hub.Env.current,
|
hub = lila.hub.Env.current,
|
||||||
|
chatApi = lila.chat.Env.current.api,
|
||||||
fishnetPlayer = lila.fishnet.Env.current.player,
|
fishnetPlayer = lila.fishnet.Env.current.player,
|
||||||
aiPerfApi = lila.fishnet.Env.current.aiPerfApi,
|
aiPerfApi = lila.fishnet.Env.current.aiPerfApi,
|
||||||
crosstableApi = lila.game.Env.current.crosstableApi,
|
crosstableApi = lila.game.Env.current.crosstableApi,
|
||||||
|
@ -318,11 +307,9 @@ object Env {
|
||||||
prefApi = lila.pref.Env.current.api,
|
prefApi = lila.pref.Env.current.api,
|
||||||
historyApi = lila.history.Env.current.api,
|
historyApi = lila.history.Env.current.api,
|
||||||
evalCache = lila.evalCache.Env.current.api,
|
evalCache = lila.evalCache.Env.current.api,
|
||||||
evalCacheHandler = lila.evalCache.Env.current.socketHandler,
|
|
||||||
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
||||||
isBotSync = lila.user.Env.current.lightUserApi.isBotSync,
|
isBotSync = lila.user.Env.current.lightUserApi.isBotSync,
|
||||||
slackApi = lila.slack.Env.current.api,
|
slackApi = lila.slack.Env.current.api,
|
||||||
ratingFactors = lila.rating.Env.current.ratingFactorsSetting.get,
|
ratingFactors = lila.rating.Env.current.ratingFactorsSetting.get
|
||||||
settingStore = lila.memo.Env.current.settingStore
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
package lila.round
|
|
||||||
|
|
||||||
import java.util.ArrayDeque
|
|
||||||
import scala.collection.JavaConverters._
|
|
||||||
|
|
||||||
import org.joda.time.DateTime
|
|
||||||
import reactivemongo.api.commands.GetLastError
|
|
||||||
import reactivemongo.bson._
|
|
||||||
|
|
||||||
import lila.db.dsl._
|
|
||||||
import lila.game.Event
|
|
||||||
import lila.socket.Socket.SocketVersion
|
|
||||||
import VersionedEvent.EpochSeconds
|
|
||||||
|
|
||||||
/**
|
|
||||||
* NOT THREAD SAFE
|
|
||||||
* Designed for use within a sequential actor (or a Duct)
|
|
||||||
*/
|
|
||||||
private final class History(
|
|
||||||
load: Fu[List[VersionedEvent]],
|
|
||||||
persist: ArrayDeque[VersionedEvent] => Unit,
|
|
||||||
deployPersistence: () => Boolean
|
|
||||||
) {
|
|
||||||
import History.Types._
|
|
||||||
|
|
||||||
// TODO: After scala 2.13, use scala's ArrayDeque
|
|
||||||
private[this] var events: ArrayDeque[VersionedEvent] = _
|
|
||||||
|
|
||||||
private[this] var versionHolder: SocketVersion = _
|
|
||||||
|
|
||||||
def getVersion: SocketVersion = {
|
|
||||||
waitForLoadedEvents
|
|
||||||
versionHolder
|
|
||||||
}
|
|
||||||
|
|
||||||
def getEventsSince(v: SocketVersion, mon: Option[lila.mon.round.history.PlatformHistory]): EventResult = {
|
|
||||||
val version = getVersion
|
|
||||||
if (v > version) VersionTooHigh
|
|
||||||
else if (v == version) UpToDate
|
|
||||||
else {
|
|
||||||
mon.foreach { m =>
|
|
||||||
m.getEventsDelta(version.value - v.value)
|
|
||||||
m.getEventsCount()
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: If version always increases by 1, it should be safe to slice
|
|
||||||
// by count instead of checking each event's version.
|
|
||||||
//
|
|
||||||
// Ugly while loop for perf, see lila-jmh-benchmarks.
|
|
||||||
val it = events.descendingIterator
|
|
||||||
var filteredEvents: List[VersionedEvent] = Nil
|
|
||||||
var e = null.asInstanceOf[VersionedEvent]
|
|
||||||
while (it.hasNext && {
|
|
||||||
e = it.next()
|
|
||||||
e.version > v
|
|
||||||
}) filteredEvents = e :: filteredEvents
|
|
||||||
|
|
||||||
filteredEvents match {
|
|
||||||
case e :: _ if e.version == v.inc => Events(filteredEvents)
|
|
||||||
case _ =>
|
|
||||||
mon.foreach(_.getEventsTooFar())
|
|
||||||
InsufficientHistory
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRecentEvents(maxEvents: Int): List[VersionedEvent] = {
|
|
||||||
waitForLoadedEvents
|
|
||||||
val it = events.descendingIterator
|
|
||||||
var evs: List[VersionedEvent] = Nil
|
|
||||||
var count = maxEvents
|
|
||||||
while (count > 0 && it.hasNext) {
|
|
||||||
evs = it.next() :: evs
|
|
||||||
count -= 1
|
|
||||||
}
|
|
||||||
evs
|
|
||||||
}
|
|
||||||
|
|
||||||
def addEvents(xs: List[Event]): List[VersionedEvent] = {
|
|
||||||
waitForLoadedEvents
|
|
||||||
val date = nowSeconds
|
|
||||||
|
|
||||||
removeTail(History.maxSize - xs.size)
|
|
||||||
pruneEvents(date - History.expireAfterSeconds)
|
|
||||||
val veBuff = List.newBuilder[VersionedEvent]
|
|
||||||
xs.foldLeft(getVersion.inc) {
|
|
||||||
case (vnext, e) =>
|
|
||||||
val ve = VersionedEvent(e, vnext, date)
|
|
||||||
events.addLast(ve)
|
|
||||||
versionHolder = vnext
|
|
||||||
veBuff += ve
|
|
||||||
vnext.inc
|
|
||||||
}
|
|
||||||
if (deployPersistence()) persist(events)
|
|
||||||
veBuff.result
|
|
||||||
}
|
|
||||||
|
|
||||||
private def removeTail(maxSize: Int) = {
|
|
||||||
if (maxSize <= 0) events.clear()
|
|
||||||
else {
|
|
||||||
var toRemove = events.size - maxSize
|
|
||||||
while (toRemove > 0) {
|
|
||||||
events.pollFirst
|
|
||||||
toRemove -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private def pruneEvents(minDate: EpochSeconds) = {
|
|
||||||
while ({
|
|
||||||
val e = events.peekFirst
|
|
||||||
(e ne null) && e.date < minDate
|
|
||||||
}) events.pollFirst
|
|
||||||
}
|
|
||||||
|
|
||||||
private def waitForLoadedEvents: Unit = {
|
|
||||||
if (events == null) {
|
|
||||||
val evs = load awaitSeconds 3
|
|
||||||
events = new ArrayDeque[VersionedEvent]
|
|
||||||
evs.foreach(events.add)
|
|
||||||
versionHolder = Option(events.peekLast).fold(SocketVersion(0))(_.version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def persistNow(): Unit = {
|
|
||||||
if (events != null) persist(events)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private object History {
|
|
||||||
object Types {
|
|
||||||
sealed trait EventResult
|
|
||||||
object VersionTooHigh extends EventResult
|
|
||||||
object UpToDate extends EventResult
|
|
||||||
object InsufficientHistory extends EventResult
|
|
||||||
final case class Events(value: List[VersionedEvent]) extends EventResult
|
|
||||||
}
|
|
||||||
|
|
||||||
private final val maxSize = 25
|
|
||||||
private final val expireAfterSeconds = 20
|
|
||||||
|
|
||||||
def apply(coll: Coll, deployPersistence: () => Boolean)(gameId: String): History = new History(
|
|
||||||
load = serverStarting ?? load(coll, gameId, deployPersistence()),
|
|
||||||
persist = persist(coll, gameId) _,
|
|
||||||
deployPersistence = deployPersistence
|
|
||||||
)
|
|
||||||
|
|
||||||
private def serverStarting = !lila.common.PlayApp.startedSinceMinutes(5)
|
|
||||||
|
|
||||||
private def load(coll: Coll, gameId: String, withPersistence: Boolean): Fu[List[VersionedEvent]] =
|
|
||||||
coll.byId[Bdoc](gameId).map { doc =>
|
|
||||||
~doc.flatMap(_.getAs[List[VersionedEvent]]("e"))
|
|
||||||
} addEffect {
|
|
||||||
case events if events.nonEmpty && !withPersistence => coll.remove($id(gameId)).void
|
|
||||||
case _ =>
|
|
||||||
}
|
|
||||||
|
|
||||||
private def persist(coll: Coll, gameId: String)(vevs: ArrayDeque[VersionedEvent]) =
|
|
||||||
if (!vevs.isEmpty) coll.update(
|
|
||||||
$doc("_id" -> gameId),
|
|
||||||
$doc(
|
|
||||||
"$set" -> $doc("e" -> vevs.toArray(Array[VersionedEvent]())),
|
|
||||||
"$setOnInsert" -> $doc("d" -> DateTime.now)
|
|
||||||
),
|
|
||||||
upsert = true,
|
|
||||||
writeConcern = GetLastError.Unacknowledged
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -4,28 +4,28 @@ import akka.actor.ActorSelection
|
||||||
|
|
||||||
import actorApi._
|
import actorApi._
|
||||||
import lila.chat.actorApi._
|
import lila.chat.actorApi._
|
||||||
import lila.chat.{ Chat, ChatTimeout }
|
import lila.chat.{ Chat, ChatApi, ChatTimeout }
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.hub.actorApi.shutup.PublicSource
|
import lila.hub.actorApi.shutup.PublicSource
|
||||||
import lila.i18n.I18nKey.{ Select => SelectI18nKey }
|
import lila.i18n.I18nKey.{ Select => SelectI18nKey }
|
||||||
import lila.i18n.{ I18nKeys, enLang }
|
import lila.i18n.{ I18nKeys, enLang }
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
final class Messenger(val chat: ActorSelection) {
|
final class Messenger(val api: ChatApi) {
|
||||||
|
|
||||||
def system(game: Game, message: SelectI18nKey, args: Any*): Unit = {
|
def system(game: Game, message: SelectI18nKey, args: Any*): Unit = {
|
||||||
val translated = message(I18nKeys).literalTxtTo(enLang, args)
|
val translated = message(I18nKeys).literalTxtTo(enLang, args)
|
||||||
chat ! SystemTalk(watcherId(Chat.Id(game.id)), translated)
|
api.userChat.system(watcherId(Chat.Id(game.id)), translated)
|
||||||
if (game.nonAi) chat ! SystemTalk(Chat.Id(game.id), translated)
|
if (game.nonAi) api.userChat.system(Chat.Id(game.id), translated)
|
||||||
}
|
}
|
||||||
|
|
||||||
def systemForOwners(chatId: Chat.Id, message: SelectI18nKey, args: Any*): Unit = {
|
def systemForOwners(chatId: Chat.Id, message: SelectI18nKey, args: Any*): Unit = {
|
||||||
val translated = message(I18nKeys).literalTxtTo(enLang, args)
|
val translated = message(I18nKeys).literalTxtTo(enLang, args)
|
||||||
chat ! SystemTalk(chatId, translated)
|
api.userChat.system(chatId, translated)
|
||||||
}
|
}
|
||||||
|
|
||||||
def watcher(chatId: Chat.Id, userId: User.ID, text: String) =
|
def watcher(chatId: Chat.Id, userId: User.ID, text: String) =
|
||||||
chat ! UserTalk(watcherId(chatId), userId, text, PublicSource.Watcher(chatId.value).some)
|
api.userChat.write(watcherId(chatId), userId, text, PublicSource.Watcher(chatId.value).some)
|
||||||
|
|
||||||
private val whisperCommands = List("/whisper ", "/w ")
|
private val whisperCommands = List("/whisper ", "/w ")
|
||||||
|
|
||||||
|
@ -33,22 +33,22 @@ final class Messenger(val chat: ActorSelection) {
|
||||||
whisperCommands.collectFirst {
|
whisperCommands.collectFirst {
|
||||||
case command if text startsWith command =>
|
case command if text startsWith command =>
|
||||||
val source = PublicSource.Watcher(chatId.value)
|
val source = PublicSource.Watcher(chatId.value)
|
||||||
UserTalk(watcherId(chatId), userId, text drop command.size, source.some)
|
api.userChat.write(watcherId(chatId), userId, text drop command.size, source.some)
|
||||||
}.orElse {
|
} getOrElse {
|
||||||
if (text startsWith "/") none // mistyped command?
|
if (!text.startsWith("/")) // mistyped command?
|
||||||
else UserTalk(chatId, userId, text, publicSource = none).some
|
api.userChat.write(chatId, userId, text, publicSource = none).some
|
||||||
} foreach chat.!
|
}
|
||||||
|
|
||||||
def owner(chatId: Chat.Id, anonColor: chess.Color, text: String): Unit =
|
def owner(chatId: Chat.Id, anonColor: chess.Color, text: String): Unit =
|
||||||
chat ! PlayerTalk(chatId, anonColor.white, text)
|
api.playerChat.write(chatId, anonColor, text)
|
||||||
|
|
||||||
// simul or tour chat from a game
|
// simul or tour chat from a game
|
||||||
def external(setup: Chat.Setup, userId: User.ID, text: String): Unit =
|
def external(setup: Chat.Setup, userId: User.ID, text: String): Unit =
|
||||||
chat ! UserTalk(setup.id, userId, text, setup.publicSource.some)
|
api.userChat.write(setup.id, userId, text, setup.publicSource.some)
|
||||||
|
|
||||||
def timeout(chatId: Chat.Id, modId: User.ID, suspect: User.ID, reason: String): Unit =
|
def timeout(chatId: Chat.Id, modId: User.ID, suspect: User.ID, reason: String): Unit =
|
||||||
ChatTimeout.Reason(reason) foreach { r =>
|
ChatTimeout.Reason(reason) foreach { r =>
|
||||||
chat ! Timeout(chatId, modId, suspect, r, local = false)
|
api.userChat.timeout(chatId, modId, suspect, r, local = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private def watcherId(chatId: Chat.Id) = Chat.Id(s"$chatId/w")
|
private def watcherId(chatId: Chat.Id) = Chat.Id(s"$chatId/w")
|
||||||
|
|
|
@ -7,15 +7,11 @@ import scala.concurrent.duration._
|
||||||
|
|
||||||
private object MoveMonitor {
|
private object MoveMonitor {
|
||||||
|
|
||||||
def start(system: ActorSystem, channel: lila.socket.Channel) =
|
def start(system: ActorSystem) =
|
||||||
|
|
||||||
Kamon.metrics.subscribe("trace", "round.move.trace", system.actorOf(Props(new Actor {
|
Kamon.metrics.subscribe("trace", "round.move.trace", system.actorOf(Props(new Actor {
|
||||||
var currentMicros: Int = 0
|
var currentMicros: Int = 0
|
||||||
context.system.scheduler.schedule(5 second, 2 second) {
|
context.system.scheduler.schedule(5 second, 2 second) {
|
||||||
channel ! lila.socket.Channel.Publish(lila.socket.Socket.makeMessage(
|
|
||||||
"mlat",
|
|
||||||
(currentMicros / 100) / 10d
|
|
||||||
))
|
|
||||||
system.lilaBus.publish(lila.hub.actorApi.round.Mlat(currentMicros), 'mlat)
|
system.lilaBus.publish(lila.hub.actorApi.round.Mlat(currentMicros), 'mlat)
|
||||||
}
|
}
|
||||||
def receive = {
|
def receive = {
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
package lila.round
|
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
import akka.actor._
|
|
||||||
import akka.pattern.{ ask, pipe }
|
|
||||||
import chess.{ Color, White, Black, Speed }
|
|
||||||
import play.api.libs.iteratee._
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
import actorApi._
|
|
||||||
import lila.chat.Chat
|
|
||||||
import lila.common.LightUser
|
|
||||||
import lila.game.actorApi.{ StartGame, UserStartGame }
|
|
||||||
import lila.game.{ Game, Event }
|
|
||||||
import lila.hub.actorApi.Deploy
|
|
||||||
import lila.hub.actorApi.round.{ IsOnGame, TourStandingOld }
|
|
||||||
import lila.hub.actorApi.simul.GetHostIds
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import lila.socket._
|
|
||||||
import lila.socket.actorApi.{ Connected => _, _ }
|
|
||||||
import lila.socket.Socket
|
|
||||||
import lila.user.User
|
|
||||||
import makeTimeout.short
|
|
||||||
|
|
||||||
object OldRoundSocket {
|
|
||||||
|
|
||||||
case class ChatIds(priv: Chat.Id, pub: Chat.Id) {
|
|
||||||
def all = Seq(priv, pub)
|
|
||||||
}
|
|
||||||
|
|
||||||
private[round] case class Dependencies(
|
|
||||||
system: ActorSystem,
|
|
||||||
lightUser: LightUser.Getter,
|
|
||||||
sriTtl: FiniteDuration,
|
|
||||||
getGame: Game.ID => Fu[Option[Game]]
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package lila.round
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.Promise
|
||||||
import ornicar.scalalib.Zero
|
import ornicar.scalalib.Zero
|
||||||
|
|
||||||
import actorApi._, round._
|
import actorApi._, round._
|
||||||
|
@ -15,7 +16,6 @@ import lila.hub.actorApi.DeployPost
|
||||||
import lila.hub.actorApi.map._
|
import lila.hub.actorApi.map._
|
||||||
import lila.hub.actorApi.round.{ FishnetPlay, FishnetStart, BotPlay, RematchYes, RematchNo, Abort, Resign, IsOnGame }
|
import lila.hub.actorApi.round.{ FishnetPlay, FishnetStart, BotPlay, RematchYes, RematchNo, Abort, Resign, IsOnGame }
|
||||||
import lila.hub.actorApi.simul.GetHostIds
|
import lila.hub.actorApi.simul.GetHostIds
|
||||||
import lila.hub.actorApi.socket.HasUserId
|
|
||||||
import lila.hub.Duct
|
import lila.hub.Duct
|
||||||
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
||||||
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
||||||
|
@ -478,6 +478,7 @@ private[round] final class RoundDuct(
|
||||||
|
|
||||||
object RoundDuct {
|
object RoundDuct {
|
||||||
|
|
||||||
|
case class HasUserId(userId: User.ID, promise: Promise[Boolean])
|
||||||
case class SetGameInfo(game: lila.game.Game, goneWeights: (Float, Float))
|
case class SetGameInfo(game: lila.game.Game, goneWeights: (Float, Float))
|
||||||
case object Tick
|
case object Tick
|
||||||
case object Stop
|
case object Stop
|
||||||
|
|
|
@ -18,7 +18,7 @@ import lila.round.actorApi.round.{ QuietFlag, Abandon }
|
||||||
private[round] final class Titivate(
|
private[round] final class Titivate(
|
||||||
tellRound: TellRound,
|
tellRound: TellRound,
|
||||||
bookmark: ActorSelection,
|
bookmark: ActorSelection,
|
||||||
chat: ActorSelection
|
chatApi: lila.chat.ChatApi
|
||||||
) extends Actor {
|
) extends Actor {
|
||||||
|
|
||||||
object Run
|
object Run
|
||||||
|
@ -59,7 +59,7 @@ private[round] final class Titivate(
|
||||||
|
|
||||||
else if (game.unplayed) {
|
else if (game.unplayed) {
|
||||||
bookmark ! lila.hub.actorApi.bookmark.Remove(game.id)
|
bookmark ! lila.hub.actorApi.bookmark.Remove(game.id)
|
||||||
chat ! lila.chat.actorApi.Remove(lila.chat.Chat.Id(game.id))
|
chatApi.remove(lila.chat.Chat.Id(game.id))
|
||||||
GameRepo remove game.id
|
GameRepo remove game.id
|
||||||
} else game.clock match {
|
} else game.clock match {
|
||||||
|
|
||||||
|
|
|
@ -1,94 +0,0 @@
|
||||||
package lila.round
|
|
||||||
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
import actorApi.Member
|
|
||||||
import chess.Color
|
|
||||||
import lila.game.Event
|
|
||||||
import lila.socket.Socket.{ SocketVersion, socketVersionFormat }
|
|
||||||
|
|
||||||
private case class VersionedEvent(
|
|
||||||
version: SocketVersion,
|
|
||||||
typ: String,
|
|
||||||
encoded: Either[String, JsValue],
|
|
||||||
only: Option[Color],
|
|
||||||
moveBy: Option[Color],
|
|
||||||
owner: Boolean,
|
|
||||||
watcher: Boolean,
|
|
||||||
troll: Boolean,
|
|
||||||
date: VersionedEvent.EpochSeconds
|
|
||||||
) {
|
|
||||||
|
|
||||||
lazy val decoded: JsValue = encoded.fold(Json.parse, identity)
|
|
||||||
|
|
||||||
def jsFor(m: Member): JsObject = if (visibleBy(m)) {
|
|
||||||
if (decoded == JsNull) Json.obj("v" -> version, "t" -> typ)
|
|
||||||
else Json.obj(
|
|
||||||
"v" -> version,
|
|
||||||
"t" -> typ,
|
|
||||||
"d" -> decodedFor(m)
|
|
||||||
)
|
|
||||||
} else Json.obj("v" -> version)
|
|
||||||
|
|
||||||
private def decodedFor(m: Member) =
|
|
||||||
if (moveBy.exists(by => m.color == by || m.watcher)) decoded.as[JsObject] - "dests"
|
|
||||||
else decoded
|
|
||||||
|
|
||||||
private def visibleBy(m: Member): Boolean =
|
|
||||||
!(watcher && m.owner) &&
|
|
||||||
!(owner && m.watcher) &&
|
|
||||||
!(troll && !m.troll) &&
|
|
||||||
only.fold(true)(c => m.owner && c == m.color)
|
|
||||||
|
|
||||||
override def toString = s"Event $version $typ"
|
|
||||||
}
|
|
||||||
|
|
||||||
private object VersionedEvent {
|
|
||||||
|
|
||||||
type EpochSeconds = Int
|
|
||||||
|
|
||||||
def apply(e: Event, v: SocketVersion, date: EpochSeconds): VersionedEvent = VersionedEvent(
|
|
||||||
version = v,
|
|
||||||
typ = e.typ,
|
|
||||||
encoded = Right(e.data),
|
|
||||||
only = e.only,
|
|
||||||
moveBy = e.moveBy,
|
|
||||||
owner = e.owner,
|
|
||||||
watcher = e.watcher,
|
|
||||||
troll = e.troll,
|
|
||||||
date = date
|
|
||||||
)
|
|
||||||
|
|
||||||
import lila.db.BSON
|
|
||||||
import reactivemongo.bson._
|
|
||||||
|
|
||||||
private implicit val SocketVersionHandler = lila.db.dsl.intAnyValHandler[SocketVersion](_.value, SocketVersion.apply)
|
|
||||||
|
|
||||||
implicit val versionedEventHandler = new BSON[VersionedEvent] {
|
|
||||||
def reads(r: BSON.Reader) = VersionedEvent(
|
|
||||||
version = r.get[SocketVersion]("v"),
|
|
||||||
typ = r str "t",
|
|
||||||
encoded = r.strO("d").map(Left.apply).getOrElse(Right(JsNull)),
|
|
||||||
only = r boolO "o" map Color.apply,
|
|
||||||
moveBy = r boolO "m" map Color.apply,
|
|
||||||
owner = r boolD "ow",
|
|
||||||
watcher = r boolD "w",
|
|
||||||
troll = r boolD "r",
|
|
||||||
date = nowSeconds
|
|
||||||
)
|
|
||||||
def writes(w: BSON.Writer, o: VersionedEvent) = BSONDocument(
|
|
||||||
"v" -> o.version,
|
|
||||||
"t" -> o.typ,
|
|
||||||
"d" -> (o.encoded match {
|
|
||||||
case Left(s) => s.some
|
|
||||||
case Right(JsNull) => none
|
|
||||||
case Right(js) => Json.stringify(js).some
|
|
||||||
}),
|
|
||||||
"o" -> o.only.map(_.white),
|
|
||||||
"m" -> o.moveBy.map(_.white),
|
|
||||||
"ow" -> w.boolO(o.owner),
|
|
||||||
"w" -> w.boolO(o.watcher),
|
|
||||||
"r" -> w.boolO(o.troll)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -8,81 +8,11 @@ import chess.{ MoveMetrics, Color }
|
||||||
|
|
||||||
import lila.common.{ IpAddress, IsMobile }
|
import lila.common.{ IpAddress, IsMobile }
|
||||||
import lila.socket.Socket.{ SocketVersion, Sri }
|
import lila.socket.Socket.{ SocketVersion, Sri }
|
||||||
import lila.socket.DirectSocketMember
|
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
import lila.game.Game.{ FullId, PlayerId }
|
import lila.game.Game.{ FullId, PlayerId }
|
||||||
|
|
||||||
case class EventList(events: List[lila.game.Event])
|
case class EventList(events: List[lila.game.Event])
|
||||||
|
|
||||||
sealed trait Member extends DirectSocketMember {
|
|
||||||
|
|
||||||
val color: Color
|
|
||||||
val playerIdOption: Option[String]
|
|
||||||
val troll: Boolean
|
|
||||||
val userTv: Option[User.ID]
|
|
||||||
|
|
||||||
def owner = playerIdOption.isDefined
|
|
||||||
def watcher = !owner
|
|
||||||
|
|
||||||
def onUserTv(userId: User.ID) = userTv has userId
|
|
||||||
}
|
|
||||||
|
|
||||||
object Member {
|
|
||||||
def apply(
|
|
||||||
channel: JsChannel,
|
|
||||||
user: Option[User],
|
|
||||||
color: Color,
|
|
||||||
playerIdOption: Option[String],
|
|
||||||
userTv: Option[User.ID]
|
|
||||||
): Member = {
|
|
||||||
val userId = user map (_.id)
|
|
||||||
val troll = user.??(_.troll)
|
|
||||||
playerIdOption.fold[Member](Watcher(channel, userId, color, troll, userTv)) { playerId =>
|
|
||||||
Owner(channel, userId, playerId, color, troll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Owner(
|
|
||||||
channel: JsChannel,
|
|
||||||
userId: Option[User.ID],
|
|
||||||
playerId: String,
|
|
||||||
color: Color,
|
|
||||||
troll: Boolean
|
|
||||||
) extends Member {
|
|
||||||
|
|
||||||
val playerIdOption = playerId.some
|
|
||||||
val userTv = none
|
|
||||||
|
|
||||||
override def toString = s"$color owner: ${userId | "anon"}"
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Watcher(
|
|
||||||
channel: JsChannel,
|
|
||||||
userId: Option[User.ID],
|
|
||||||
color: Color,
|
|
||||||
troll: Boolean,
|
|
||||||
userTv: Option[User.ID]
|
|
||||||
) extends Member {
|
|
||||||
|
|
||||||
val playerIdOption = none
|
|
||||||
|
|
||||||
override def toString = s"$color watcher: ${userId | "anon"}"
|
|
||||||
}
|
|
||||||
|
|
||||||
case class Join(
|
|
||||||
sri: Sri,
|
|
||||||
user: Option[User],
|
|
||||||
color: Color,
|
|
||||||
playerId: Option[String],
|
|
||||||
userTv: Option[UserTv],
|
|
||||||
version: Option[SocketVersion],
|
|
||||||
mobile: IsMobile,
|
|
||||||
promise: Promise[Connected]
|
|
||||||
)
|
|
||||||
case class UserTv(userId: User.ID, reload: Fu[Boolean])
|
case class UserTv(userId: User.ID, reload: Fu[Boolean])
|
||||||
case class Connected(enumerator: JsEnumerator, member: Member)
|
|
||||||
case class Bye(color: Color)
|
|
||||||
case class ByePlayer(playerId: PlayerId)
|
case class ByePlayer(playerId: PlayerId)
|
||||||
case class IsGone(color: Color, promise: Promise[Boolean])
|
case class IsGone(color: Color, promise: Promise[Boolean])
|
||||||
case class GetSocketStatus(promise: Promise[SocketStatus])
|
case class GetSocketStatus(promise: Promise[SocketStatus])
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
import lila.game.Event
|
import lila.game.Event
|
||||||
import lila.socket.WithSocket
|
|
||||||
|
|
||||||
package object round extends PackageObject with WithSocket {
|
package object round extends PackageObject {
|
||||||
|
|
||||||
private[round] type Events = List[Event]
|
private[round] type Events = List[Event]
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
import lila.socket.WithSocket
|
package object setup extends PackageObject {
|
||||||
|
|
||||||
package object setup extends PackageObject with WithSocket {
|
private[setup] val logger = lila.log("setup")
|
||||||
|
|
||||||
private[setup] def logger = lila.log("setup")
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@ import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.hub.{ Duct, DuctMap }
|
import lila.hub.{ Duct, DuctMap }
|
||||||
import lila.socket.History
|
|
||||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
|
@ -15,6 +14,7 @@ final class Env(
|
||||||
scheduler: lila.common.Scheduler,
|
scheduler: lila.common.Scheduler,
|
||||||
db: lila.db.Env,
|
db: lila.db.Env,
|
||||||
hub: lila.hub.Env,
|
hub: lila.hub.Env,
|
||||||
|
chatApi: lila.chat.ChatApi,
|
||||||
lightUser: lila.common.LightUser.Getter,
|
lightUser: lila.common.LightUser.Getter,
|
||||||
onGameStart: String => Unit,
|
onGameStart: String => Unit,
|
||||||
isOnline: String => Boolean,
|
isOnline: String => Boolean,
|
||||||
|
@ -49,7 +49,7 @@ final class Env(
|
||||||
getSimul = repo.find,
|
getSimul = repo.find,
|
||||||
jsonView = jsonView,
|
jsonView = jsonView,
|
||||||
remoteSocketApi = remoteSocketApi,
|
remoteSocketApi = remoteSocketApi,
|
||||||
chat = hub.chat,
|
chat = chatApi,
|
||||||
bus = system.lilaBus
|
bus = system.lilaBus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ object Env {
|
||||||
scheduler = lila.common.PlayApp.scheduler,
|
scheduler = lila.common.PlayApp.scheduler,
|
||||||
db = lila.db.Env.current,
|
db = lila.db.Env.current,
|
||||||
hub = lila.hub.Env.current,
|
hub = lila.hub.Env.current,
|
||||||
|
chatApi = lila.chat.Env.current.api,
|
||||||
lightUser = lila.user.Env.current.lightUser,
|
lightUser = lila.user.Env.current.lightUser,
|
||||||
onGameStart = lila.round.Env.current.onStart,
|
onGameStart = lila.round.Env.current.onStart,
|
||||||
isOnline = lila.user.Env.current.isOnline,
|
isOnline = lila.user.Env.current.isOnline,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import lila.hub.actorApi.lobby.ReloadSimuls
|
||||||
import lila.hub.actorApi.map.Tell
|
import lila.hub.actorApi.map.Tell
|
||||||
import lila.hub.actorApi.timeline.{ Propagate, SimulCreate, SimulJoin }
|
import lila.hub.actorApi.timeline.{ Propagate, SimulCreate, SimulJoin }
|
||||||
import lila.hub.{ Duct, DuctMap }
|
import lila.hub.{ Duct, DuctMap }
|
||||||
import lila.socket.actorApi.SendToFlag
|
import lila.socket.Socket.SendToFlag
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
import makeTimeout.short
|
import makeTimeout.short
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package lila.simul
|
package lila.simul
|
||||||
|
|
||||||
import akka.actor.ActorSelection
|
|
||||||
import play.api.libs.json._
|
import play.api.libs.json._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
@ -14,7 +13,7 @@ private final class SimulSocket(
|
||||||
getSimul: Simul.ID => Fu[Option[Simul]],
|
getSimul: Simul.ID => Fu[Option[Simul]],
|
||||||
jsonView: JsonView,
|
jsonView: JsonView,
|
||||||
remoteSocketApi: lila.socket.RemoteSocket,
|
remoteSocketApi: lila.socket.RemoteSocket,
|
||||||
chat: ActorSelection,
|
chat: lila.chat.ChatApi,
|
||||||
bus: lila.common.Bus
|
bus: lila.common.Bus
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -5,38 +5,14 @@ import scala.concurrent.Promise
|
||||||
|
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.socket.Socket.{ Sri, SocketVersion }
|
import lila.socket.Socket.{ Sri, SocketVersion }
|
||||||
import lila.socket.DirectSocketMember
|
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
private[simul] case class SimulSocketMember(
|
|
||||||
channel: JsChannel,
|
|
||||||
userId: Option[String],
|
|
||||||
troll: Boolean
|
|
||||||
) extends DirectSocketMember
|
|
||||||
|
|
||||||
private[simul] object SimulSocketMember {
|
|
||||||
def apply(channel: JsChannel, user: Option[User]): SimulSocketMember = SimulSocketMember(
|
|
||||||
channel = channel,
|
|
||||||
userId = user map (_.id),
|
|
||||||
troll = user.??(_.troll)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private[simul] case class Messadata(trollish: Boolean = false)
|
|
||||||
|
|
||||||
private[simul] case class Join(
|
|
||||||
sri: Sri,
|
|
||||||
user: Option[User],
|
|
||||||
version: Option[SocketVersion],
|
|
||||||
promise: Promise[Connected]
|
|
||||||
)
|
|
||||||
private[simul] case class Talk(tourId: String, u: String, t: String, troll: Boolean)
|
private[simul] case class Talk(tourId: String, u: String, t: String, troll: Boolean)
|
||||||
private[simul] case class StartGame(game: Game, hostId: String)
|
private[simul] case class StartGame(game: Game, hostId: String)
|
||||||
private[simul] case class StartSimul(firstGame: Game, hostId: String)
|
private[simul] case class StartSimul(firstGame: Game, hostId: String)
|
||||||
private[simul] case class HostIsOn(gameId: String)
|
private[simul] case class HostIsOn(gameId: String)
|
||||||
private[simul] case object Reload
|
private[simul] case object Reload
|
||||||
private[simul] case object Aborted
|
private[simul] case object Aborted
|
||||||
private[simul] case class Connected(enumerator: JsEnumerator, member: SimulSocketMember)
|
|
||||||
|
|
||||||
private[simul] case object NotifyCrowd
|
private[simul] case object NotifyCrowd
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
import lila.socket.WithSocket
|
package object simul extends PackageObject {
|
||||||
|
|
||||||
package object simul extends PackageObject with WithSocket {
|
|
||||||
|
|
||||||
private[simul] object RandomName {
|
private[simul] object RandomName {
|
||||||
|
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import actorApi.SocketLeave
|
|
||||||
import akka.actor.ActorSystem
|
|
||||||
import play.api.libs.json.JsValue
|
|
||||||
|
|
||||||
import lila.hub.Trouper
|
|
||||||
|
|
||||||
// TODO remove after lila-ws
|
|
||||||
final class Channel(system: ActorSystem) extends Trouper {
|
|
||||||
|
|
||||||
system.lilaBus.subscribe(this, 'socketLeave)
|
|
||||||
|
|
||||||
import Channel._
|
|
||||||
|
|
||||||
private val members = scala.collection.mutable.Set.empty[SocketMember]
|
|
||||||
|
|
||||||
val process: Trouper.Receive = {
|
|
||||||
|
|
||||||
case SocketLeave(_, member) => members -= member
|
|
||||||
|
|
||||||
case Sub(member) => members += member
|
|
||||||
|
|
||||||
case UnSub(member) => members -= member
|
|
||||||
|
|
||||||
case Publish(msg) => members.foreach(_ push msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object Channel {
|
|
||||||
|
|
||||||
case class Sub(member: SocketMember)
|
|
||||||
case class UnSub(member: SocketMember)
|
|
||||||
|
|
||||||
case class Publish(msg: JsValue)
|
|
||||||
}
|
|
|
@ -5,8 +5,6 @@ import com.typesafe.config.Config
|
||||||
import io.lettuce.core._
|
import io.lettuce.core._
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import actorApi._
|
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -16,22 +14,15 @@ final class Env(
|
||||||
|
|
||||||
private val RedisUri = config getString "redis.uri"
|
private val RedisUri = config getString "redis.uri"
|
||||||
|
|
||||||
val population = new SocketPopulation(system)
|
|
||||||
|
|
||||||
private val moveBroadcast = new MoveBroadcast(system)
|
|
||||||
|
|
||||||
private val userRegister = new UserRegister(system)
|
|
||||||
|
|
||||||
val remoteSocket = new RemoteSocket(
|
val remoteSocket = new RemoteSocket(
|
||||||
redisClient = RedisClient create RedisURI.create(RedisUri),
|
redisClient = RedisClient create RedisURI.create(RedisUri),
|
||||||
notificationActor = hub.notification,
|
notificationActor = hub.notification,
|
||||||
setNb = nb => population ! actorApi.RemoteNbMembers(nb),
|
|
||||||
bus = system.lilaBus,
|
bus = system.lilaBus,
|
||||||
lifecycle = lifecycle
|
lifecycle = lifecycle
|
||||||
)
|
)
|
||||||
remoteSocket.subscribe("site-in", RemoteSocket.Protocol.In.baseReader)(remoteSocket.baseHandler)
|
remoteSocket.subscribe("site-in", RemoteSocket.Protocol.In.baseReader)(remoteSocket.baseHandler)
|
||||||
|
|
||||||
system.scheduler.schedule(5 seconds, 1 seconds) { population ! PopulationTell }
|
val onlineUserIds: () => Set[String] = () => remoteSocket.onlineUserIds.get
|
||||||
}
|
}
|
||||||
|
|
||||||
object Env {
|
object Env {
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import chess.opening._
|
|
||||||
import chess.variant.Variant
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
object GetOpening {
|
|
||||||
|
|
||||||
import lila.tree.Node.openingWriter
|
|
||||||
|
|
||||||
def apply(o: JsObject): Option[JsObject] = for {
|
|
||||||
d <- o obj "d"
|
|
||||||
variant = chess.variant.Variant orDefault ~d.str("variant")
|
|
||||||
fen <- d str "fen"
|
|
||||||
path <- d str "path"
|
|
||||||
opening <- Variant.openingSensibleVariants(variant) ?? {
|
|
||||||
FullOpeningDB findByFen fen
|
|
||||||
}
|
|
||||||
} yield Json.obj(
|
|
||||||
"path" -> path,
|
|
||||||
"opening" -> opening
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,137 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import akka.actor.ActorRef
|
|
||||||
import akka.pattern.ask
|
|
||||||
import ornicar.scalalib.Zero
|
|
||||||
import play.api.libs.iteratee.Iteratee
|
|
||||||
import play.api.libs.json._
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
import actorApi._
|
|
||||||
import chess.Centis
|
|
||||||
import lila.common.ApiVersion
|
|
||||||
import lila.common.PimpedJson.centisReads
|
|
||||||
import lila.hub.actorApi.relation.ReloadOnlineFriends
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import lila.socket.Socket.makeMessage
|
|
||||||
|
|
||||||
object Handler {
|
|
||||||
|
|
||||||
type Controller = PartialFunction[(String, JsObject), Unit]
|
|
||||||
type Connection = (Controller, JsEnumerator, SocketMember)
|
|
||||||
type ActorConnecter = PartialFunction[Any, Connection]
|
|
||||||
type TrouperConnecter = PartialFunction[Any, Connection]
|
|
||||||
|
|
||||||
private val AnaRateLimiter = new lila.memo.RateLimit[String](120, 30 seconds,
|
|
||||||
name = "socket analysis move",
|
|
||||||
key = "socket_analysis_move")
|
|
||||||
|
|
||||||
def AnaRateLimit[A: Zero](sri: Socket.Sri, member: SocketMember)(op: => A) =
|
|
||||||
AnaRateLimiter(sri.value, msg = s"user: ${member.userId | "anon"}")(op)
|
|
||||||
|
|
||||||
type OnPing = (SocketTrouper[_], SocketMember, Socket.Sri, ApiVersion) => Unit
|
|
||||||
|
|
||||||
val defaultOnPing: OnPing = (socket, member, sri, apiVersion) => {
|
|
||||||
socket setAlive sri
|
|
||||||
member push {
|
|
||||||
if (apiVersion gte 4) Socket.emptyPong
|
|
||||||
else Socket.initialPong
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def iteratee(
|
|
||||||
hub: lila.hub.Env,
|
|
||||||
controller: Controller,
|
|
||||||
member: SocketMember,
|
|
||||||
socket: SocketTrouper[_],
|
|
||||||
sri: Socket.Sri,
|
|
||||||
apiVersion: ApiVersion,
|
|
||||||
onPing: OnPing = defaultOnPing
|
|
||||||
): JsIteratee = {
|
|
||||||
val fullCtrl = controller orElse baseController(hub, socket, member, sri, apiVersion, onPing)
|
|
||||||
Iteratee.foreach[JsValue] { v =>
|
|
||||||
if (!socket.getIsAlive) {
|
|
||||||
// this socket is dead, ignore message and tell client to reconnect to the new socket
|
|
||||||
lila.mon.socket.deadMsg()
|
|
||||||
member push SocketTrouper.resyncMessage
|
|
||||||
} // process null ping immediately
|
|
||||||
else if (v == JsNull) onPing(socket, member, sri, apiVersion)
|
|
||||||
else for {
|
|
||||||
obj <- v.asOpt[JsObject]
|
|
||||||
t <- (obj \ "t").asOpt[String]
|
|
||||||
} fullCtrl(t -> obj)
|
|
||||||
}
|
|
||||||
// Unfortunately this map function is only called
|
|
||||||
// if the JS closes the socket with lichess.socket.disconnect()
|
|
||||||
// but not if the tab is closed or browsed away!
|
|
||||||
// Also if the client loses Internet connection,
|
|
||||||
// this will only be called after Internet is restored,
|
|
||||||
// and it can be called after a reconnection (using same sri) was performed,
|
|
||||||
// effectively quitting the reconnected client.
|
|
||||||
.map(_ => socket ! Quit(sri, member))
|
|
||||||
}
|
|
||||||
|
|
||||||
def recordUserLagFromPing(member: SocketMember, ping: JsObject) = for {
|
|
||||||
user <- member.userId
|
|
||||||
lag <- (ping \ "l").asOpt[Centis]
|
|
||||||
} UserLagCache.put(user, lag)
|
|
||||||
|
|
||||||
private def baseController(
|
|
||||||
hub: lila.hub.Env,
|
|
||||||
socket: SocketTrouper[_],
|
|
||||||
member: SocketMember,
|
|
||||||
sri: Socket.Sri,
|
|
||||||
apiVersion: ApiVersion,
|
|
||||||
onPing: OnPing
|
|
||||||
): Controller = {
|
|
||||||
// latency ping, or BC mobile app ping
|
|
||||||
case ("p", o) =>
|
|
||||||
onPing(socket, member, sri, apiVersion)
|
|
||||||
recordUserLagFromPing(member, o)
|
|
||||||
case ("following_onlines", _) => member.userId foreach { u =>
|
|
||||||
hub.relation ! ReloadOnlineFriends(u)
|
|
||||||
}
|
|
||||||
case ("startWatching", o) => o str "d" foreach { ids =>
|
|
||||||
hub.bus.publish(StartWatching(sri, member, ids.split(' ').toSet), 'socketMoveBroadcast)
|
|
||||||
}
|
|
||||||
case ("moveLat", o) => hub.bus.publish(
|
|
||||||
if (~(o boolean "d")) Channel.Sub(member) else Channel.UnSub(member),
|
|
||||||
'roundMoveTimeChannel
|
|
||||||
)
|
|
||||||
case ("anaMove", o) => AnaRateLimit(sri, member) {
|
|
||||||
AnaMove parse o foreach { anaMove =>
|
|
||||||
member push {
|
|
||||||
anaMove.branch match {
|
|
||||||
case scalaz.Success(node) => makeMessage("node", anaMove json node)
|
|
||||||
case scalaz.Failure(err) => makeMessage("stepFailure", err.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ("anaDrop", o) => AnaRateLimit(sri, member) {
|
|
||||||
AnaDrop parse o foreach { anaDrop =>
|
|
||||||
anaDrop.branch match {
|
|
||||||
case scalaz.Success(branch) =>
|
|
||||||
member push makeMessage("node", anaDrop json branch)
|
|
||||||
case scalaz.Failure(err) =>
|
|
||||||
member push makeMessage("stepFailure", err.toString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ("anaDests", o) => AnaRateLimit(sri, member) {
|
|
||||||
AnaDests parse o foreach { res =>
|
|
||||||
member push makeMessage("dests", res.json)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ("opening", o) => AnaRateLimit(sri, member) {
|
|
||||||
GetOpening(o) foreach { res =>
|
|
||||||
member push makeMessage("opening", res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ("notified", _) => member.userId foreach { userId =>
|
|
||||||
hub.notification ! lila.hub.actorApi.notify.Notified(userId)
|
|
||||||
}
|
|
||||||
case _ => // logwarn("Unhandled msg: " + msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import play.api.libs.iteratee._
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
trait Historical[M <: SocketMember, Metadata] { self: SocketTrouper[M] =>
|
|
||||||
|
|
||||||
protected val history: History[Metadata]
|
|
||||||
|
|
||||||
protected type Message = History.Message[Metadata]
|
|
||||||
|
|
||||||
protected def shouldSkipMessageFor(message: Message, member: M): Boolean
|
|
||||||
|
|
||||||
def notifyVersion[A: Writes](t: String, data: A, metadata: Metadata): Unit = {
|
|
||||||
val vmsg = history.+=(makeMessage(t, data), metadata)
|
|
||||||
val send = sendMessage(vmsg) _
|
|
||||||
members foreachValue send
|
|
||||||
}
|
|
||||||
|
|
||||||
def filteredMessage(member: M)(message: Message) =
|
|
||||||
if (shouldSkipMessageFor(message, member)) message.skipMsg
|
|
||||||
else message.fullMsg
|
|
||||||
|
|
||||||
def sendMessage(message: Message)(member: M): Unit =
|
|
||||||
member push filteredMessage(member)(message)
|
|
||||||
|
|
||||||
def sendMessage(member: M)(message: Message): Unit =
|
|
||||||
member push filteredMessage(member)(message)
|
|
||||||
|
|
||||||
protected def prependEventsSince(
|
|
||||||
since: Option[Socket.SocketVersion],
|
|
||||||
enum: Enumerator[JsValue],
|
|
||||||
member: M
|
|
||||||
): Enumerator[JsValue] =
|
|
||||||
lila.common.Iteratee.prepend(getEventsSince(since, member), enum)
|
|
||||||
|
|
||||||
protected def getEventsSince(since: Option[Socket.SocketVersion], member: M): List[JsValue] =
|
|
||||||
since
|
|
||||||
.fold(history.getRecent.some)(history.since)
|
|
||||||
.fold(List(SocketTrouper.resyncMessage))(_ map filteredMessage(member))
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import com.github.blemale.scaffeine.{ Cache, Scaffeine }
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
|
||||||
|
|
||||||
import play.api.libs.json._
|
|
||||||
|
|
||||||
import Socket.SocketVersion
|
|
||||||
|
|
||||||
final class History[Metadata](ttl: FiniteDuration) {
|
|
||||||
|
|
||||||
type Message = History.Message[Metadata]
|
|
||||||
|
|
||||||
private var privateVersion = SocketVersion(0)
|
|
||||||
|
|
||||||
private val cache: Cache[Int, Message] = Scaffeine()
|
|
||||||
.expireAfterWrite(ttl)
|
|
||||||
.build[Int, Message]
|
|
||||||
|
|
||||||
def version = privateVersion
|
|
||||||
|
|
||||||
// none if version asked is > to history version
|
|
||||||
// none if an event is missing (asked too old version)
|
|
||||||
def since(v: SocketVersion): Option[List[Message]] =
|
|
||||||
if (v > version) None
|
|
||||||
else if (v == version) Some(Nil)
|
|
||||||
else {
|
|
||||||
val msgs: List[Message] = (v.inc.value to version.value).flatMap(message)(scala.collection.breakOut)
|
|
||||||
(msgs.size == version.value - v.value) option msgs
|
|
||||||
}
|
|
||||||
|
|
||||||
def getRecent: List[Message] = {
|
|
||||||
val v = version.value
|
|
||||||
(v - 5 to v).flatMap(message)(scala.collection.breakOut)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def message(v: Int) = cache getIfPresent v
|
|
||||||
|
|
||||||
def +=(payload: JsObject, metadata: Metadata): Message = {
|
|
||||||
privateVersion = privateVersion.inc
|
|
||||||
val vmsg = History.Message(payload, privateVersion, metadata)
|
|
||||||
cache.put(privateVersion.value, vmsg)
|
|
||||||
vmsg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object History {
|
|
||||||
|
|
||||||
case class Message[Metadata](payload: JsObject, version: SocketVersion, metadata: Metadata) {
|
|
||||||
|
|
||||||
lazy val fullMsg = payload + ("v" -> JsNumber(version.value))
|
|
||||||
|
|
||||||
lazy val skipMsg = Json.obj("v" -> version.value)
|
|
||||||
|
|
||||||
def ++(obj: JsObject) = copy(payload = payload ++ obj)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import scala.collection.mutable.AnyRefMap
|
|
||||||
|
|
||||||
/*
|
|
||||||
* NOT thread safe
|
|
||||||
* Use in an actor
|
|
||||||
*/
|
|
||||||
final class MemberGroup[M <: SocketMember](groupOf: M => Option[String]) {
|
|
||||||
|
|
||||||
private type Group = String
|
|
||||||
private type SriString = String
|
|
||||||
|
|
||||||
private val groups = AnyRefMap.empty[Group, AnyRefMap[SriString, M]]
|
|
||||||
|
|
||||||
def add(sri: Socket.Sri, member: M): Unit = groupOf(member) foreach { group =>
|
|
||||||
groups get group match {
|
|
||||||
case None => groups += (group -> AnyRefMap(sri.value -> member))
|
|
||||||
case Some(members) => members += (sri.value -> member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def remove(sri: Socket.Sri, member: M): Unit = groupOf(member) foreach { group =>
|
|
||||||
groups get group foreach { members =>
|
|
||||||
members -= sri.value
|
|
||||||
if (members.isEmpty) groups -= group
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
def get(group: Group) = groups get group
|
|
||||||
|
|
||||||
def keys = groups.keys
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import scala.collection.mutable.AnyRefMap
|
|
||||||
|
|
||||||
import actorApi.{ SocketLeave, StartWatching }
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import lila.hub.actorApi.round.MoveEvent
|
|
||||||
|
|
||||||
private final class MoveBroadcast(system: akka.actor.ActorSystem) extends Trouper {
|
|
||||||
|
|
||||||
system.lilaBus.subscribe(this, 'moveEvent, 'socketLeave, 'socketMoveBroadcast)
|
|
||||||
|
|
||||||
private type SriString = String
|
|
||||||
private type GameId = String
|
|
||||||
|
|
||||||
private case class WatchingMember(member: SocketMember, gameIds: Set[GameId])
|
|
||||||
|
|
||||||
private val members = AnyRefMap.empty[SriString, WatchingMember]
|
|
||||||
private val games = AnyRefMap.empty[GameId, Set[SriString]]
|
|
||||||
|
|
||||||
val process: Trouper.Receive = {
|
|
||||||
|
|
||||||
case MoveEvent(gameId, fen, move) =>
|
|
||||||
games get gameId foreach { mIds =>
|
|
||||||
val msg = Socket.makeMessage("fen", play.api.libs.json.Json.obj(
|
|
||||||
"id" -> gameId,
|
|
||||||
"fen" -> fen,
|
|
||||||
"lm" -> move
|
|
||||||
))
|
|
||||||
mIds foreach { mId =>
|
|
||||||
members get mId foreach (_.member push msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case StartWatching(sri, member, gameIds) =>
|
|
||||||
members += (sri.value -> WatchingMember(member, gameIds ++ members.get(sri.value).??(_.gameIds)))
|
|
||||||
gameIds foreach { id =>
|
|
||||||
games += (id -> (~games.get(id) + sri.value))
|
|
||||||
}
|
|
||||||
|
|
||||||
case SocketLeave(sri, _) => members get sri.value foreach { m =>
|
|
||||||
members -= sri.value
|
|
||||||
m.gameIds foreach { id =>
|
|
||||||
games get id foreach { sris =>
|
|
||||||
val newSris = sris - sri.value
|
|
||||||
if (newSris.isEmpty) games -= id
|
|
||||||
else games += (id -> newSris)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,16 +15,14 @@ import lila.hub.actorApi.relation.ReloadOnlineFriends
|
||||||
import lila.hub.actorApi.round.{ MoveEvent, Mlat }
|
import lila.hub.actorApi.round.{ MoveEvent, Mlat }
|
||||||
import lila.hub.actorApi.security.CloseAccount
|
import lila.hub.actorApi.security.CloseAccount
|
||||||
import lila.hub.actorApi.socket.remote.{ TellSriIn, TellSriOut }
|
import lila.hub.actorApi.socket.remote.{ TellSriIn, TellSriOut }
|
||||||
import lila.hub.actorApi.socket.{ SendTo, SendTos, WithUserIds }
|
import lila.hub.actorApi.socket.{ SendTo, SendTos }
|
||||||
import lila.hub.actorApi.{ Deploy, Announce }
|
import lila.hub.actorApi.{ Deploy, Announce }
|
||||||
import lila.hub.{ TrouperMap, Trouper }
|
import lila.hub.{ TrouperMap, Trouper }
|
||||||
import lila.socket.actorApi.SendToFlag
|
import Socket.{ SocketVersion, GetVersion, Sri, SendToFlag }
|
||||||
import Socket.{ SocketVersion, GetVersion, Sri }
|
|
||||||
|
|
||||||
final class RemoteSocket(
|
final class RemoteSocket(
|
||||||
redisClient: RedisClient,
|
redisClient: RedisClient,
|
||||||
notificationActor: akka.actor.ActorSelection,
|
notificationActor: akka.actor.ActorSelection,
|
||||||
setNb: Int => Unit,
|
|
||||||
bus: lila.common.Bus,
|
bus: lila.common.Bus,
|
||||||
lifecycle: play.api.inject.ApplicationLifecycle
|
lifecycle: play.api.inject.ApplicationLifecycle
|
||||||
) {
|
) {
|
||||||
|
@ -43,32 +41,31 @@ final class RemoteSocket(
|
||||||
promise.future map readRes
|
promise.future map readRes
|
||||||
}
|
}
|
||||||
|
|
||||||
private val connectedUserIds = new AtomicReference(Set.empty[String])
|
val onlineUserIds: AtomicReference[Set[String]] = new AtomicReference(Set("lichess"))
|
||||||
|
|
||||||
private val watchedGameIds = collection.mutable.Set.empty[String]
|
private val watchedGameIds = collection.mutable.Set.empty[String]
|
||||||
|
|
||||||
val baseHandler: Handler = {
|
val baseHandler: Handler = {
|
||||||
case In.ConnectUser(userId) =>
|
case In.ConnectUser(userId) =>
|
||||||
bus.publish(lila.hub.actorApi.socket.remote.ConnectUser(userId), 'userActive)
|
bus.publish(lila.hub.actorApi.socket.remote.ConnectUser(userId), 'userActive)
|
||||||
connectedUserIds.getAndUpdate((x: UserIds) => x + userId)
|
onlineUserIds.getAndUpdate((x: UserIds) => x + userId)
|
||||||
case In.DisconnectUsers(userIds) =>
|
case In.DisconnectUsers(userIds) =>
|
||||||
connectedUserIds.getAndUpdate((x: UserIds) => x -- userIds)
|
onlineUserIds.getAndUpdate((x: UserIds) => x -- userIds)
|
||||||
case In.Watch(gameId) => watchedGameIds += gameId
|
case In.Watch(gameId) => watchedGameIds += gameId
|
||||||
case In.Unwatch(gameId) => watchedGameIds -= gameId
|
case In.Unwatch(gameId) => watchedGameIds -= gameId
|
||||||
case In.NotifiedBatch(userIds) => notificationActor ! lila.hub.actorApi.notify.NotifiedBatch(userIds)
|
case In.NotifiedBatch(userIds) => notificationActor ! lila.hub.actorApi.notify.NotifiedBatch(userIds)
|
||||||
case In.Connections(nb) => tick(nb)
|
|
||||||
case In.FriendsBatch(userIds) => userIds foreach { userId =>
|
case In.FriendsBatch(userIds) => userIds foreach { userId =>
|
||||||
bus.publish(ReloadOnlineFriends(userId), 'reloadOnlineFriends)
|
bus.publish(ReloadOnlineFriends(userId), 'reloadOnlineFriends)
|
||||||
}
|
}
|
||||||
case In.Lags(lags) =>
|
case In.Lags(lags) =>
|
||||||
lags foreach (UserLagCache.put _).tupled
|
lags foreach (UserLagCache.put _).tupled
|
||||||
// this shouldn't be necessary... ensure that users are known to be online
|
// this shouldn't be necessary... ensure that users are known to be online
|
||||||
connectedUserIds.getAndUpdate((x: UserIds) => x ++ lags.keys)
|
onlineUserIds.getAndUpdate((x: UserIds) => x ++ lags.keys)
|
||||||
case In.TellSri(sri, userId, typ, msg) =>
|
case In.TellSri(sri, userId, typ, msg) =>
|
||||||
bus.publish(TellSriIn(sri.value, userId, msg), Symbol(s"remoteSocketIn:$typ"))
|
bus.publish(TellSriIn(sri.value, userId, msg), Symbol(s"remoteSocketIn:$typ"))
|
||||||
case In.WsBoot =>
|
case In.WsBoot =>
|
||||||
logger.warn("Remote socket boot")
|
logger.warn("Remote socket boot")
|
||||||
connectedUserIds set Set.empty
|
onlineUserIds set Set("lichess")
|
||||||
watchedGameIds.clear
|
watchedGameIds.clear
|
||||||
case In.ReqResponse(reqId, response) =>
|
case In.ReqResponse(reqId, response) =>
|
||||||
requests.computeIfPresent(reqId, (_: Int, promise: Promise[String]) => {
|
requests.computeIfPresent(reqId, (_: Int, promise: Promise[String]) => {
|
||||||
|
@ -81,36 +78,26 @@ final class RemoteSocket(
|
||||||
case MoveEvent(gameId, fen, move) =>
|
case MoveEvent(gameId, fen, move) =>
|
||||||
if (watchedGameIds(gameId)) send(Out.move(gameId, move, fen))
|
if (watchedGameIds(gameId)) send(Out.move(gameId, move, fen))
|
||||||
case SendTos(userIds, payload) =>
|
case SendTos(userIds, payload) =>
|
||||||
val connectedUsers = userIds intersect connectedUserIds.get
|
val connectedUsers = userIds intersect onlineUserIds.get
|
||||||
if (connectedUsers.nonEmpty) send(Out.tellUsers(connectedUsers, payload))
|
if (connectedUsers.nonEmpty) send(Out.tellUsers(connectedUsers, payload))
|
||||||
case SendTo(userId, payload) if connectedUserIds.get.contains(userId) =>
|
case SendTo(userId, payload) if onlineUserIds.get.contains(userId) =>
|
||||||
send(Out.tellUser(userId, payload))
|
send(Out.tellUser(userId, payload))
|
||||||
case Announce(_, _, json) =>
|
case Announce(_, _, json) =>
|
||||||
send(Out.tellAll(Json.obj("t" -> "announce", "d" -> json)))
|
send(Out.tellAll(Json.obj("t" -> "announce", "d" -> json)))
|
||||||
case Mlat(micros) =>
|
case Mlat(micros) =>
|
||||||
send(Out.mlat(micros))
|
send(Out.mlat(micros))
|
||||||
case actorApi.SendToFlag(flag, payload) =>
|
case Socket.SendToFlag(flag, payload) =>
|
||||||
send(Out.tellFlag(flag, payload))
|
send(Out.tellFlag(flag, payload))
|
||||||
case TellSriOut(sri, payload) =>
|
case TellSriOut(sri, payload) =>
|
||||||
send(Out.tellSri(Sri(sri), payload))
|
send(Out.tellSri(Sri(sri), payload))
|
||||||
case CloseAccount(userId) =>
|
case CloseAccount(userId) =>
|
||||||
send(Out.disconnectUser(userId))
|
send(Out.disconnectUser(userId))
|
||||||
case WithUserIds(f) =>
|
|
||||||
f(connectedUserIds.get)
|
|
||||||
case lila.hub.actorApi.mod.Shadowban(userId, v) =>
|
case lila.hub.actorApi.mod.Shadowban(userId, v) =>
|
||||||
send(Out.setTroll(userId, v))
|
send(Out.setTroll(userId, v))
|
||||||
case lila.hub.actorApi.mod.Impersonate(userId, modId) =>
|
case lila.hub.actorApi.mod.Impersonate(userId, modId) =>
|
||||||
send(Out.impersonate(userId, modId))
|
send(Out.impersonate(userId, modId))
|
||||||
}
|
}
|
||||||
|
|
||||||
private def tick(nbConn: Int): Unit = {
|
|
||||||
setNb(nbConn)
|
|
||||||
mon.sets.games(watchedGameIds.size)
|
|
||||||
mon.sets.users(connectedUserIds.get.size)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val mon = lila.mon.socket.remote
|
|
||||||
|
|
||||||
def makeSender(channel: Channel): Sender = new Sender(redisClient.connectPubSub(), channel)
|
def makeSender(channel: Channel): Sender = new Sender(redisClient.connectPubSub(), channel)
|
||||||
|
|
||||||
private val send: Send = makeSender("site-out").apply _
|
private val send: Send = makeSender("site-out").apply _
|
||||||
|
@ -141,6 +128,8 @@ final class RemoteSocket(
|
||||||
|
|
||||||
object RemoteSocket {
|
object RemoteSocket {
|
||||||
|
|
||||||
|
private val logger = lila log "socket"
|
||||||
|
|
||||||
type Send = String => Unit
|
type Send = String => Unit
|
||||||
|
|
||||||
final class Sender(conn: StatefulRedisPubSubConnection[String, String], channel: Channel) {
|
final class Sender(conn: StatefulRedisPubSubConnection[String, String], channel: Channel) {
|
||||||
|
@ -173,7 +162,6 @@ object RemoteSocket {
|
||||||
case class Watch(gameId: String) extends In
|
case class Watch(gameId: String) extends In
|
||||||
case class Unwatch(gameId: String) extends In
|
case class Unwatch(gameId: String) extends In
|
||||||
case class NotifiedBatch(userIds: Iterable[String]) extends In
|
case class NotifiedBatch(userIds: Iterable[String]) extends In
|
||||||
case class Connections(nb: Int) extends In
|
|
||||||
case class Lag(userId: String, lag: Centis) extends In
|
case class Lag(userId: String, lag: Centis) extends In
|
||||||
case class Lags(lags: Map[String, Centis]) extends In
|
case class Lags(lags: Map[String, Centis]) extends In
|
||||||
case class FriendsBatch(userIds: Iterable[String]) extends In
|
case class FriendsBatch(userIds: Iterable[String]) extends In
|
||||||
|
@ -192,7 +180,6 @@ object RemoteSocket {
|
||||||
case "watch" => Watch(raw.args).some
|
case "watch" => Watch(raw.args).some
|
||||||
case "unwatch" => Unwatch(raw.args).some
|
case "unwatch" => Unwatch(raw.args).some
|
||||||
case "notified/batch" => NotifiedBatch(commas(raw.args)).some
|
case "notified/batch" => NotifiedBatch(commas(raw.args)).some
|
||||||
case "connections" => parseIntOption(raw.args) map Connections.apply
|
|
||||||
case "lag" => raw.all |> { s => s lift 1 flatMap parseIntOption map Centis.apply map { Lag(s(0), _) } }
|
case "lag" => raw.all |> { s => s lift 1 flatMap parseIntOption map Centis.apply map { Lag(s(0), _) } }
|
||||||
case "lags" => Lags(commas(raw.args).flatMap {
|
case "lags" => Lags(commas(raw.args).flatMap {
|
||||||
_ split ':' match {
|
_ split ':' match {
|
||||||
|
|
|
@ -24,6 +24,8 @@ object Socket extends Socket {
|
||||||
|
|
||||||
case class GetVersion(promise: Promise[SocketVersion])
|
case class GetVersion(promise: Promise[SocketVersion])
|
||||||
|
|
||||||
|
case class SendToFlag(flag: String, message: JsObject)
|
||||||
|
|
||||||
val initialPong = makeMessage("n")
|
val initialPong = makeMessage("n")
|
||||||
val emptyPong = JsNumber(0)
|
val emptyPong = JsNumber(0)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import ornicar.scalalib.Random.approximatly
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
|
|
||||||
import lila.hub.{ Trouper, TrouperMap }
|
|
||||||
|
|
||||||
object SocketMap {
|
|
||||||
|
|
||||||
def apply[T <: Trouper](
|
|
||||||
system: akka.actor.ActorSystem,
|
|
||||||
mkTrouper: String => T,
|
|
||||||
accessTimeout: FiniteDuration,
|
|
||||||
monitoringName: String,
|
|
||||||
broomFrequency: FiniteDuration
|
|
||||||
): TrouperMap[T] = {
|
|
||||||
|
|
||||||
val trouperMap = new TrouperMap[T](
|
|
||||||
mkTrouper = mkTrouper,
|
|
||||||
accessTimeout = accessTimeout
|
|
||||||
)
|
|
||||||
|
|
||||||
system.scheduler.schedule(30 seconds, 30 seconds) {
|
|
||||||
trouperMap.monitor(monitoringName)
|
|
||||||
}
|
|
||||||
system.scheduler.schedule(approximatly(0.05f)(12.seconds.toMillis).millis, broomFrequency) {
|
|
||||||
trouperMap tellAll actorApi.Broom
|
|
||||||
}
|
|
||||||
system.lilaBus.subscribeFuns(
|
|
||||||
'shutdown -> {
|
|
||||||
case _ => trouperMap.killAll
|
|
||||||
},
|
|
||||||
'announce -> {
|
|
||||||
case m: lila.hub.actorApi.Announce => trouperMap tellAll m
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
trouperMap
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import play.api.libs.json.{ JsObject, JsValue }
|
|
||||||
|
|
||||||
trait SocketMember {
|
|
||||||
|
|
||||||
val userId: Option[String]
|
|
||||||
def isAuth = userId.isDefined
|
|
||||||
|
|
||||||
val push: SocketMember.Push
|
|
||||||
|
|
||||||
def end: Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
trait DirectSocketMember extends SocketMember {
|
|
||||||
|
|
||||||
protected val channel: JsChannel
|
|
||||||
|
|
||||||
val push: SocketMember.Push = channel.push _
|
|
||||||
|
|
||||||
def end = channel.end
|
|
||||||
}
|
|
||||||
|
|
||||||
trait RemoteSocketMember extends SocketMember {
|
|
||||||
|
|
||||||
def end = () // meh
|
|
||||||
}
|
|
||||||
|
|
||||||
object SocketMember {
|
|
||||||
type Push = JsValue => Unit
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import actorApi.{ SocketEnter, SocketLeave, PopulationTell, NbMembers, RemoteNbMembers }
|
|
||||||
|
|
||||||
final class SocketPopulation(system: akka.actor.ActorSystem) extends Trouper {
|
|
||||||
|
|
||||||
private var nb = 0
|
|
||||||
private var remoteNb = 0
|
|
||||||
|
|
||||||
system.lilaBus.subscribe(this, 'socketEnter, 'socketLeave)
|
|
||||||
|
|
||||||
val process: Trouper.Receive = {
|
|
||||||
|
|
||||||
case _: SocketEnter =>
|
|
||||||
nb = nb + 1
|
|
||||||
lila.mon.socket.open()
|
|
||||||
|
|
||||||
case _: SocketLeave =>
|
|
||||||
nb = nb - 1
|
|
||||||
lila.mon.socket.close()
|
|
||||||
|
|
||||||
case RemoteNbMembers(r) => remoteNb = r
|
|
||||||
|
|
||||||
case PopulationTell => system.lilaBus.publish(NbMembers(nb + remoteNb), 'nbMembers)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import ornicar.scalalib.Random.approximatly
|
|
||||||
import play.api.libs.json._
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
import scala.concurrent.Promise
|
|
||||||
|
|
||||||
import actorApi._
|
|
||||||
import chess.Centis
|
|
||||||
import lila.common.LightUser
|
|
||||||
import lila.hub.actorApi.{ Deploy, Announce }
|
|
||||||
import lila.hub.actorApi.socket.HasUserId
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import lila.memo.ExpireSetMemo
|
|
||||||
|
|
||||||
abstract class SocketTrouper[M <: SocketMember](
|
|
||||||
protected val system: akka.actor.ActorSystem,
|
|
||||||
protected val sriTtl: Duration
|
|
||||||
) extends Socket with Trouper {
|
|
||||||
|
|
||||||
import SocketTrouper._
|
|
||||||
|
|
||||||
/* Do not eject members on stop!
|
|
||||||
* It does not always instruct the client to disconnect (!)
|
|
||||||
* But it does prevent from sending it more messages.
|
|
||||||
* If the client isn't disconnected, we can't tell it to resync
|
|
||||||
* when we receive more messages from it!
|
|
||||||
* In theory a socket should only stop when all clients are gone anyway.
|
|
||||||
*/
|
|
||||||
// override def stop() = {
|
|
||||||
// super.stop()
|
|
||||||
// members foreachKey ejectSriString
|
|
||||||
// }
|
|
||||||
|
|
||||||
protected val receiveTrouper: PartialFunction[Any, Unit] = {
|
|
||||||
|
|
||||||
case HasUserId(userId, promise) => promise success hasUserId(userId)
|
|
||||||
|
|
||||||
case GetNbMembers(promise) => promise success members.size
|
|
||||||
}
|
|
||||||
|
|
||||||
val process = receiveSpecific orElse receiveTrouper orElse receiveGeneric
|
|
||||||
|
|
||||||
// expose so the handler can call without going through process, during ping
|
|
||||||
def setAlive(sri: Socket.Sri): Unit = aliveSris put sri.value
|
|
||||||
|
|
||||||
protected val members = scala.collection.mutable.AnyRefMap.empty[String, M]
|
|
||||||
protected val aliveSris = new ExpireSetMemo(sriTtl)
|
|
||||||
|
|
||||||
protected def lilaBus = system.lilaBus
|
|
||||||
|
|
||||||
// to be defined in subclassing socket
|
|
||||||
protected def receiveSpecific: PartialFunction[Any, Unit]
|
|
||||||
|
|
||||||
// generic message handler
|
|
||||||
protected def receiveGeneric: PartialFunction[Any, Unit] = {
|
|
||||||
|
|
||||||
case Broom => broom
|
|
||||||
|
|
||||||
// when a member quits
|
|
||||||
case Quit(sri, member) => withMember(sri) { m =>
|
|
||||||
if (m eq member) quit(sri, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
case Resync(sri) => resync(sri)
|
|
||||||
|
|
||||||
case d: Deploy => onDeploy(d)
|
|
||||||
|
|
||||||
case Announce(_, _, json) => notifyAll(makeMessage("announce", json))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def hasUserId(userId: String) = members.values.exists(_.userId contains userId)
|
|
||||||
|
|
||||||
protected def notifyAll[A: Writes](t: String, data: A): Unit =
|
|
||||||
notifyAll(makeMessage(t, data))
|
|
||||||
|
|
||||||
protected def notifyAll(t: String): Unit =
|
|
||||||
notifyAll(makeMessage(t))
|
|
||||||
|
|
||||||
protected def notifyAll(msg: JsObject): Unit =
|
|
||||||
members.foreachValue(_ push msg)
|
|
||||||
|
|
||||||
protected def notifyIf(msg: JsObject)(condition: M => Boolean): Unit =
|
|
||||||
members.foreachValue { member =>
|
|
||||||
if (condition(member)) member push msg
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def notifyMember[A: Writes](t: String, data: A)(member: M): Unit = {
|
|
||||||
member push makeMessage(t, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def notifySri[A: Writes](t: String, data: A)(sri: Socket.Sri): Unit = {
|
|
||||||
withMember(sri)(_ push makeMessage(t, data))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def broom: Unit =
|
|
||||||
members foreachKey { sri =>
|
|
||||||
if (!aliveSris.get(sri)) ejectSriString(sri)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def ejectSriString(sri: String): Unit = eject(Socket.Sri(sri))
|
|
||||||
|
|
||||||
// actively boot a member, if it exists
|
|
||||||
// this function is called when a member joins,
|
|
||||||
// to prevent duplicate sri
|
|
||||||
protected final def eject(sri: Socket.Sri): Unit = withMember(sri) { eject(sri, _) }
|
|
||||||
protected final def eject(sri: Socket.Sri, member: M): Unit = {
|
|
||||||
member.end
|
|
||||||
quit(sri, member)
|
|
||||||
}
|
|
||||||
|
|
||||||
// when a member quits, voluntarily or not
|
|
||||||
// at this point we know the member exists
|
|
||||||
private final def quit(sri: Socket.Sri, member: M): Unit = {
|
|
||||||
members -= sri.value
|
|
||||||
lilaBus.publish(SocketLeave(sri, member), 'socketLeave)
|
|
||||||
afterQuit(sri, member)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def afterQuit(sri: Socket.Sri, member: M): Unit = {}
|
|
||||||
|
|
||||||
protected def onDeploy(d: Deploy): Unit =
|
|
||||||
notifyAll(makeMessage(d.key))
|
|
||||||
|
|
||||||
protected def resync(member: M): Unit = {
|
|
||||||
import scala.concurrent.duration._
|
|
||||||
system.scheduler.scheduleOnce((scala.util.Random nextInt 2000).milliseconds) {
|
|
||||||
resyncNow(member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def resync(sri: Socket.Sri): Unit =
|
|
||||||
withMember(sri)(resync)
|
|
||||||
|
|
||||||
def resyncNow(member: M): Unit =
|
|
||||||
member push resyncMessage
|
|
||||||
|
|
||||||
protected def addMember(sri: Socket.Sri, member: M): Unit = {
|
|
||||||
eject(sri)
|
|
||||||
members += (sri.value -> member)
|
|
||||||
setAlive(sri)
|
|
||||||
lilaBus.publish(SocketEnter(sri, member), 'socketEnter)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def membersByUserId(userId: String): Iterable[M] = members collect {
|
|
||||||
case (_, member) if member.userId.contains(userId) => member
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def firstMemberByUserId(userId: String): Option[M] = members collectFirst {
|
|
||||||
case (_, member) if member.userId.contains(userId) => member
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def sriToUserId(sri: Socket.Sri): Option[String] = members get sri.value flatMap (_.userId)
|
|
||||||
|
|
||||||
protected val maxSpectatorUsers = 15
|
|
||||||
|
|
||||||
protected def showSpectators(lightUser: LightUser.Getter)(watchers: Iterable[SocketMember]): Fu[Option[JsValue]] = watchers.size match {
|
|
||||||
case 0 => fuccess(none)
|
|
||||||
case s if s > maxSpectatorUsers => fuccess(Json.obj("nb" -> s).some)
|
|
||||||
case s => {
|
|
||||||
val userIdsWithDups = watchers.toSeq.flatMap(_.userId)
|
|
||||||
val anons = s - userIdsWithDups.size
|
|
||||||
val userIds = userIdsWithDups.distinct
|
|
||||||
|
|
||||||
val total = anons + userIds.size
|
|
||||||
|
|
||||||
userIds.map(lightUser).sequenceFu.map { users =>
|
|
||||||
Json.obj(
|
|
||||||
"nb" -> total,
|
|
||||||
"users" -> users.flatMap(_.map(_.titleName)),
|
|
||||||
"anons" -> anons
|
|
||||||
).some
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected def withMember(sri: Socket.Sri)(f: M => Unit): Unit = members get sri.value foreach f
|
|
||||||
}
|
|
||||||
|
|
||||||
object SocketTrouper extends Socket {
|
|
||||||
case class GetNbMembers(promise: Promise[Int])
|
|
||||||
|
|
||||||
val resyncMessage = makeMessage("resync")
|
|
||||||
}
|
|
|
@ -5,6 +5,7 @@ import com.github.blemale.scaffeine.{ Cache, Scaffeine }
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
object UserLagCache {
|
object UserLagCache {
|
||||||
|
|
||||||
private val cache: Cache[String, Centis] = Scaffeine()
|
private val cache: Cache[String, Centis] = Scaffeine()
|
||||||
.expireAfterWrite(15 minutes)
|
.expireAfterWrite(15 minutes)
|
||||||
.build[String, Centis]
|
.build[String, Centis]
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import play.api.libs.json.JsObject
|
|
||||||
|
|
||||||
import actorApi.{ SocketLeave, SocketEnter }
|
|
||||||
import lila.hub.Trouper
|
|
||||||
import lila.hub.actorApi.socket.{ SendTo, SendTos, WithUserIds }
|
|
||||||
import lila.hub.actorApi.security.CloseAccount
|
|
||||||
|
|
||||||
private final class UserRegister(system: akka.actor.ActorSystem) extends Trouper {
|
|
||||||
|
|
||||||
system.lilaBus.subscribe(this, 'socketEnter, 'socketLeave, 'accountClose, 'socketUsers)
|
|
||||||
|
|
||||||
private val users = new MemberGroup[SocketMember](_.userId)
|
|
||||||
|
|
||||||
val process: Trouper.Receive = {
|
|
||||||
|
|
||||||
case SocketEnter(sri, member) => users.add(sri, member)
|
|
||||||
|
|
||||||
case SocketLeave(sri, member) => users.remove(sri, member)
|
|
||||||
|
|
||||||
case SendTo(userId, msg) => sendTo(userId, msg)
|
|
||||||
|
|
||||||
case SendTos(userIds, msg) => userIds foreach { sendTo(_, msg) }
|
|
||||||
|
|
||||||
case WithUserIds(f) => f(users.keys)
|
|
||||||
|
|
||||||
case CloseAccount(userId) => userDo(userId)(_.end)
|
|
||||||
}
|
|
||||||
|
|
||||||
private def sendTo(userId: String, msg: JsObject): Unit =
|
|
||||||
userDo(userId)(_ push msg)
|
|
||||||
|
|
||||||
private def userDo(userId: String)(f: SocketMember => Unit): Unit =
|
|
||||||
users get userId foreach { _ foreachValue f }
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
|
|
||||||
import play.api.libs.iteratee.{ Iteratee, Enumerator, Concurrent }
|
|
||||||
import play.api.libs.json.JsValue
|
|
||||||
|
|
||||||
trait WithSocket {
|
|
||||||
|
|
||||||
type JsChannel = Concurrent.Channel[JsValue]
|
|
||||||
type JsEnumerator = Enumerator[JsValue]
|
|
||||||
type JsIteratee = Iteratee[JsValue, _]
|
|
||||||
type JsSocketHandler = (JsIteratee, JsEnumerator)
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package lila.socket
|
|
||||||
package actorApi
|
|
||||||
|
|
||||||
import play.api.libs.json.JsObject
|
|
||||||
|
|
||||||
case class Connected(enumerator: JsEnumerator, member: SocketMember)
|
|
||||||
|
|
||||||
private[socket] case object Broom
|
|
||||||
private[socket] case class Quit(sri: Socket.Sri, member: SocketMember)
|
|
||||||
|
|
||||||
case class SocketEnter(sri: Socket.Sri, member: SocketMember)
|
|
||||||
case class SocketLeave(sri: Socket.Sri, member: SocketMember)
|
|
||||||
|
|
||||||
case class Resync(sri: Socket.Sri)
|
|
||||||
|
|
||||||
case class SendToFlag(flag: String, message: JsObject)
|
|
||||||
|
|
||||||
case object PopulationTell
|
|
||||||
case class NbMembers(nb: Int)
|
|
||||||
case class RemoteNbMembers(nb: Int)
|
|
||||||
|
|
||||||
case class StartWatching(sri: Socket.Sri, member: SocketMember, gameIds: Set[String])
|
|
|
@ -1,6 +1,3 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
package object socket extends PackageObject with socket.WithSocket {
|
package object socket extends PackageObject
|
||||||
|
|
||||||
val logger = lila.log("socket")
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ package lila.study
|
||||||
import chess.format.pgn.Tags
|
import chess.format.pgn.Tags
|
||||||
import chess.format.{ Forsyth, FEN }
|
import chess.format.{ Forsyth, FEN }
|
||||||
import chess.variant.{ Variant, Crazyhouse }
|
import chess.variant.{ Variant, Crazyhouse }
|
||||||
import lila.chat.Chat
|
import lila.chat.{ Chat, ChatApi }
|
||||||
import lila.game.{ Game, GameRepo, Namer }
|
import lila.game.{ Game, GameRepo, Namer }
|
||||||
import lila.importer.Importer
|
import lila.importer.Importer
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
@ -11,7 +11,7 @@ import lila.user.User
|
||||||
private final class ChapterMaker(
|
private final class ChapterMaker(
|
||||||
domain: String,
|
domain: String,
|
||||||
lightUser: lila.user.LightUserApi,
|
lightUser: lila.user.LightUserApi,
|
||||||
chat: akka.actor.ActorSelection,
|
chatApi: ChatApi,
|
||||||
importer: Importer,
|
importer: Importer,
|
||||||
pgnFetch: PgnFetch,
|
pgnFetch: PgnFetch,
|
||||||
pgnDump: lila.game.PgnDump
|
pgnDump: lila.game.PgnDump
|
||||||
|
@ -138,7 +138,7 @@ private final class ChapterMaker(
|
||||||
|
|
||||||
def notifyChat(study: Study, game: Game, userId: User.ID) =
|
def notifyChat(study: Study, game: Game, userId: User.ID) =
|
||||||
if (study.isPublic) List(game.id, s"${game.id}/w") foreach { chatId =>
|
if (study.isPublic) List(game.id, s"${game.id}/w") foreach { chatId =>
|
||||||
chat ! lila.chat.actorApi.UserTalk(
|
chatApi.userChat.write(
|
||||||
chatId = Chat.Id(chatId),
|
chatId = Chat.Id(chatId),
|
||||||
userId = userId,
|
userId = userId,
|
||||||
text = s"I'm studying this game on ${domain}/study/${study.id}",
|
text = s"I'm studying this game on ${domain}/study/${study.id}",
|
||||||
|
|
|
@ -4,7 +4,6 @@ import akka.actor._
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.hub.actorApi.socket.HasUserId
|
|
||||||
import lila.hub.{ Duct, DuctMap }
|
import lila.hub.{ Duct, DuctMap }
|
||||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
@ -16,13 +15,13 @@ final class Env(
|
||||||
divider: lila.game.Divider,
|
divider: lila.game.Divider,
|
||||||
importer: lila.importer.Importer,
|
importer: lila.importer.Importer,
|
||||||
explorerImporter: lila.explorer.ExplorerImporter,
|
explorerImporter: lila.explorer.ExplorerImporter,
|
||||||
evalCacheHandler: lila.evalCache.EvalCacheSocketHandler,
|
|
||||||
notifyApi: lila.notify.NotifyApi,
|
notifyApi: lila.notify.NotifyApi,
|
||||||
getPref: User => Fu[lila.pref.Pref],
|
getPref: User => Fu[lila.pref.Pref],
|
||||||
getRelation: (User.ID, User.ID) => Fu[Option[lila.relation.Relation]],
|
getRelation: (User.ID, User.ID) => Fu[Option[lila.relation.Relation]],
|
||||||
remoteSocketApi: lila.socket.RemoteSocket,
|
remoteSocketApi: lila.socket.RemoteSocket,
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
hub: lila.hub.Env,
|
hub: lila.hub.Env,
|
||||||
|
chatApi: lila.chat.ChatApi,
|
||||||
db: lila.db.Env,
|
db: lila.db.Env,
|
||||||
asyncCache: lila.memo.AsyncCache.Builder
|
asyncCache: lila.memo.AsyncCache.Builder
|
||||||
) {
|
) {
|
||||||
|
@ -48,7 +47,7 @@ final class Env(
|
||||||
jsonView = jsonView,
|
jsonView = jsonView,
|
||||||
lightStudyCache = lightStudyCache,
|
lightStudyCache = lightStudyCache,
|
||||||
remoteSocketApi = remoteSocketApi,
|
remoteSocketApi = remoteSocketApi,
|
||||||
chat = hub.chat,
|
chatApi = chatApi,
|
||||||
bus = system.lilaBus
|
bus = system.lilaBus
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -67,7 +66,7 @@ final class Env(
|
||||||
pgnFetch = new PgnFetch,
|
pgnFetch = new PgnFetch,
|
||||||
pgnDump = gamePgnDump,
|
pgnDump = gamePgnDump,
|
||||||
lightUser = lightUserApi,
|
lightUser = lightUserApi,
|
||||||
chat = hub.chat,
|
chatApi = chatApi,
|
||||||
domain = NetDomain
|
domain = NetDomain
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -121,7 +120,7 @@ final class Env(
|
||||||
explorerGameHandler = explorerGame,
|
explorerGameHandler = explorerGame,
|
||||||
lightUser = lightUserApi.sync,
|
lightUser = lightUserApi.sync,
|
||||||
scheduler = system.scheduler,
|
scheduler = system.scheduler,
|
||||||
chat = hub.chat,
|
chatApi = chatApi,
|
||||||
bus = system.lilaBus,
|
bus = system.lilaBus,
|
||||||
timeline = hub.timeline,
|
timeline = hub.timeline,
|
||||||
serverEvalRequester = serverEvalRequester,
|
serverEvalRequester = serverEvalRequester,
|
||||||
|
@ -174,13 +173,13 @@ object Env {
|
||||||
divider = lila.game.Env.current.divider,
|
divider = lila.game.Env.current.divider,
|
||||||
importer = lila.importer.Env.current.importer,
|
importer = lila.importer.Env.current.importer,
|
||||||
explorerImporter = lila.explorer.Env.current.importer,
|
explorerImporter = lila.explorer.Env.current.importer,
|
||||||
evalCacheHandler = lila.evalCache.Env.current.socketHandler,
|
|
||||||
notifyApi = lila.notify.Env.current.api,
|
notifyApi = lila.notify.Env.current.api,
|
||||||
getPref = lila.pref.Env.current.api.getPref,
|
getPref = lila.pref.Env.current.api.getPref,
|
||||||
getRelation = lila.relation.Env.current.api.fetchRelation,
|
getRelation = lila.relation.Env.current.api.fetchRelation,
|
||||||
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
||||||
system = lila.common.PlayApp.system,
|
system = lila.common.PlayApp.system,
|
||||||
hub = lila.hub.Env.current,
|
hub = lila.hub.Env.current,
|
||||||
|
chatApi = lila.chat.Env.current.api,
|
||||||
db = lila.db.Env.current,
|
db = lila.db.Env.current,
|
||||||
asyncCache = lila.memo.Env.current.asyncCache
|
asyncCache = lila.memo.Env.current.asyncCache
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
||||||
import actorApi.Who
|
import actorApi.Who
|
||||||
import chess.Centis
|
import chess.Centis
|
||||||
import chess.format.pgn.{ Tags, Glyph }
|
import chess.format.pgn.{ Tags, Glyph }
|
||||||
import lila.chat.Chat
|
import lila.chat.{ Chat, ChatApi }
|
||||||
import lila.hub.actorApi.map.Tell
|
import lila.hub.actorApi.map.Tell
|
||||||
import lila.hub.actorApi.timeline.{ Propagate, StudyCreate, StudyLike }
|
import lila.hub.actorApi.timeline.{ Propagate, StudyCreate, StudyLike }
|
||||||
import lila.socket.Socket.Sri
|
import lila.socket.Socket.Sri
|
||||||
|
@ -24,7 +24,7 @@ final class StudyApi(
|
||||||
explorerGameHandler: ExplorerGame,
|
explorerGameHandler: ExplorerGame,
|
||||||
lightUser: lila.common.LightUser.GetterSync,
|
lightUser: lila.common.LightUser.GetterSync,
|
||||||
scheduler: akka.actor.Scheduler,
|
scheduler: akka.actor.Scheduler,
|
||||||
chat: ActorSelection,
|
chatApi: ChatApi,
|
||||||
bus: lila.common.Bus,
|
bus: lila.common.Bus,
|
||||||
timeline: ActorSelection,
|
timeline: ActorSelection,
|
||||||
serverEvalRequester: ServerEval.Requester,
|
serverEvalRequester: ServerEval.Requester,
|
||||||
|
@ -127,7 +127,7 @@ final class StudyApi(
|
||||||
newChapters.headOption.map(study1.rewindTo) ?? { study =>
|
newChapters.headOption.map(study1.rewindTo) ?? { study =>
|
||||||
studyRepo.insert(study) >>
|
studyRepo.insert(study) >>
|
||||||
newChapters.map(chapterRepo.insert).sequenceFu >>- {
|
newChapters.map(chapterRepo.insert).sequenceFu >>- {
|
||||||
chat ! lila.chat.actorApi.SystemTalk(
|
chatApi.userChat.system(
|
||||||
Chat.Id(study.id.value),
|
Chat.Id(study.id.value),
|
||||||
s"Cloned from lichess.org/study/${prev.id}"
|
s"Cloned from lichess.org/study/${prev.id}"
|
||||||
)
|
)
|
||||||
|
@ -157,7 +157,7 @@ final class StudyApi(
|
||||||
def talk(userId: User.ID, studyId: Study.Id, text: String) = byId(studyId) foreach {
|
def talk(userId: User.ID, studyId: Study.Id, text: String) = byId(studyId) foreach {
|
||||||
_ foreach { study =>
|
_ foreach { study =>
|
||||||
(study canChat userId) ?? {
|
(study canChat userId) ?? {
|
||||||
chat ! lila.chat.actorApi.UserTalk(
|
chatApi.userChat.write(
|
||||||
Chat.Id(studyId.value),
|
Chat.Id(studyId.value),
|
||||||
userId = userId,
|
userId = userId,
|
||||||
text = text,
|
text = text,
|
||||||
|
@ -719,7 +719,7 @@ final class StudyApi(
|
||||||
}
|
}
|
||||||
|
|
||||||
def erase(user: User) = studyRepo.allIdsByOwner(user.id) flatMap { ids =>
|
def erase(user: User) = studyRepo.allIdsByOwner(user.id) flatMap { ids =>
|
||||||
chat ! lila.chat.actorApi.RemoveAll(ids.map(id => Chat.Id(id.value)))
|
chatApi.removeAll(ids.map(id => Chat.Id(id.value)))
|
||||||
studyRepo.deleteByIds(ids) >>
|
studyRepo.deleteByIds(ids) >>
|
||||||
chapterRepo.deleteByStudyIds(ids)
|
chapterRepo.deleteByStudyIds(ids)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@ import akka.actor._
|
||||||
import akka.pattern.ask
|
import akka.pattern.ask
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.hub.actorApi.socket.HasUserId
|
|
||||||
import lila.notify.{ InvitedToStudy, NotifyApi, Notification }
|
import lila.notify.{ InvitedToStudy, NotifyApi, Notification }
|
||||||
import lila.pref.Pref
|
import lila.pref.Pref
|
||||||
import lila.relation.{ Block, Follow }
|
import lila.relation.{ Block, Follow }
|
||||||
|
|
|
@ -11,7 +11,7 @@ import chess.format.pgn.{ Glyph, Glyphs }
|
||||||
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
||||||
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
||||||
import lila.socket.Socket.{ Sri, makeMessage }
|
import lila.socket.Socket.{ Sri, makeMessage }
|
||||||
import lila.socket.{ AnaMove, AnaDrop, AnaAny, SocketTrouper, History, Historical, AnaDests, DirectSocketMember }
|
import lila.socket.{ AnaMove, AnaDrop, AnaAny, AnaDests }
|
||||||
import lila.tree.Node.{ Shape, Shapes, Comment, Gamebook }
|
import lila.tree.Node.{ Shape, Shapes, Comment, Gamebook }
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ private final class StudySocket(
|
||||||
jsonView: JsonView,
|
jsonView: JsonView,
|
||||||
lightStudyCache: LightStudyCache,
|
lightStudyCache: LightStudyCache,
|
||||||
remoteSocketApi: lila.socket.RemoteSocket,
|
remoteSocketApi: lila.socket.RemoteSocket,
|
||||||
chat: ActorSelection,
|
chatApi: lila.chat.ChatApi,
|
||||||
bus: lila.common.Bus
|
bus: lila.common.Bus
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -185,7 +185,7 @@ private final class StudySocket(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private lazy val rHandler: Handler = roomHandler(rooms, chat, logger,
|
private lazy val rHandler: Handler = roomHandler(rooms, chatApi, logger,
|
||||||
roomId => _ => none, // the "talk" event is handled by the study API
|
roomId => _ => none, // the "talk" event is handled by the study API
|
||||||
localTimeout = Some { (roomId, modId, suspectId) =>
|
localTimeout = Some { (roomId, modId, suspectId) =>
|
||||||
api.isContributor(roomId, modId) >>& !api.isMember(roomId, suspectId)
|
api.isContributor(roomId, modId) >>& !api.isMember(roomId, suspectId)
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package lila
|
package lila
|
||||||
|
|
||||||
import lila.socket.WithSocket
|
|
||||||
import lila.hub.TrouperMap
|
import lila.hub.TrouperMap
|
||||||
|
|
||||||
package object study extends PackageObject with WithSocket {
|
package object study extends PackageObject {
|
||||||
|
|
||||||
private[study] val logger = lila.log("study")
|
private[study] val logger = lila.log("study")
|
||||||
|
|
||||||
|
|
|
@ -7,10 +7,8 @@ import scala.concurrent.Promise
|
||||||
|
|
||||||
import lila.game.Game
|
import lila.game.Game
|
||||||
import lila.hub.{ Duct, DuctMap, TrouperMap }
|
import lila.hub.{ Duct, DuctMap, TrouperMap }
|
||||||
import lila.socket.History
|
|
||||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||||
import lila.user.User
|
import lila.user.User
|
||||||
import makeTimeout.short
|
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
config: Config,
|
config: Config,
|
||||||
|
@ -21,6 +19,7 @@ final class Env(
|
||||||
proxyGame: Game.ID => Fu[Option[Game]],
|
proxyGame: Game.ID => Fu[Option[Game]],
|
||||||
flood: lila.security.Flood,
|
flood: lila.security.Flood,
|
||||||
hub: lila.hub.Env,
|
hub: lila.hub.Env,
|
||||||
|
chatApi: lila.chat.ChatApi,
|
||||||
tellRound: lila.round.TellRound,
|
tellRound: lila.round.TellRound,
|
||||||
lightUserApi: lila.user.LightUserApi,
|
lightUserApi: lila.user.LightUserApi,
|
||||||
isOnline: User.ID => Boolean,
|
isOnline: User.ID => Boolean,
|
||||||
|
@ -85,7 +84,7 @@ final class Env(
|
||||||
|
|
||||||
private val socket = new TournamentSocket(
|
private val socket = new TournamentSocket(
|
||||||
remoteSocketApi = remoteSocketApi,
|
remoteSocketApi = remoteSocketApi,
|
||||||
chat = hub.chat,
|
chat = chatApi,
|
||||||
system = system
|
system = system
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -193,6 +192,7 @@ object Env {
|
||||||
proxyGame = lila.round.Env.current.proxy.game _,
|
proxyGame = lila.round.Env.current.proxy.game _,
|
||||||
flood = lila.security.Env.current.flood,
|
flood = lila.security.Env.current.flood,
|
||||||
hub = lila.hub.Env.current,
|
hub = lila.hub.Env.current,
|
||||||
|
chatApi = lila.chat.Env.current.api,
|
||||||
tellRound = lila.round.Env.current.tellRound,
|
tellRound = lila.round.Env.current.tellRound,
|
||||||
lightUserApi = lila.user.Env.current.lightUserApi,
|
lightUserApi = lila.user.Env.current.lightUserApi,
|
||||||
isOnline = lila.user.Env.current.isOnline,
|
isOnline = lila.user.Env.current.isOnline,
|
||||||
|
|
|
@ -16,7 +16,7 @@ import lila.hub.actorApi.timeline.{ Propagate, TourJoin }
|
||||||
import lila.hub.lightTeam._
|
import lila.hub.lightTeam._
|
||||||
import lila.hub.{ Duct, DuctMap }
|
import lila.hub.{ Duct, DuctMap }
|
||||||
import lila.round.actorApi.round.{ GoBerserk, AbortForce }
|
import lila.round.actorApi.round.{ GoBerserk, AbortForce }
|
||||||
import lila.socket.actorApi.SendToFlag
|
import lila.socket.Socket.SendToFlag
|
||||||
import lila.user.{ User, UserRepo }
|
import lila.user.{ User, UserRepo }
|
||||||
import makeTimeout.short
|
import makeTimeout.short
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import lila.user.User
|
||||||
|
|
||||||
private final class TournamentSocket(
|
private final class TournamentSocket(
|
||||||
remoteSocketApi: lila.socket.RemoteSocket,
|
remoteSocketApi: lila.socket.RemoteSocket,
|
||||||
chat: ActorSelection,
|
chat: lila.chat.ChatApi,
|
||||||
system: ActorSystem
|
system: ActorSystem
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
|
|
@ -22,13 +22,9 @@ final class Env(
|
||||||
|
|
||||||
private val FeaturedSelect = config duration "featured.select"
|
private val FeaturedSelect = config duration "featured.select"
|
||||||
|
|
||||||
private val selectChannel = new lila.socket.Channel(system)
|
|
||||||
system.lilaBus.subscribe(selectChannel, 'tvSelectChannel)
|
|
||||||
|
|
||||||
private val tvTrouper = new TvTrouper(
|
private val tvTrouper = new TvTrouper(
|
||||||
system,
|
system,
|
||||||
hub.renderer,
|
hub.renderer,
|
||||||
selectChannel,
|
|
||||||
lightUser,
|
lightUser,
|
||||||
onSelect,
|
onSelect,
|
||||||
proxyGame,
|
proxyGame,
|
||||||
|
|
|
@ -13,7 +13,6 @@ import lila.hub.Trouper
|
||||||
private[tv] final class TvTrouper(
|
private[tv] final class TvTrouper(
|
||||||
system: ActorSystem,
|
system: ActorSystem,
|
||||||
rendererActor: ActorSelection,
|
rendererActor: ActorSelection,
|
||||||
selectChannel: lila.socket.Channel,
|
|
||||||
lightUser: LightUser.GetterSync,
|
lightUser: LightUser.GetterSync,
|
||||||
onSelect: Game => Unit,
|
onSelect: Game => Unit,
|
||||||
proxyGame: Game.ID => Fu[Option[Game]],
|
proxyGame: Game.ID => Fu[Option[Game]],
|
||||||
|
@ -75,8 +74,7 @@ private[tv] final class TvTrouper(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
selectChannel ! lila.socket.Channel.Publish(makeMessage("tvSelect", data)) // TODO remove: old rounds
|
system.lilaBus.publish(lila.hub.actorApi.tv.TvSelect(game.id, game.speed, data), 'tvSelect)
|
||||||
system.lilaBus.publish(lila.hub.actorApi.tv.TvSelect(game.id, game.speed, data), 'tvSelect) // new rounds
|
|
||||||
if (channel == Tv.Channel.Best) {
|
if (channel == Tv.Channel.Best) {
|
||||||
implicit def timeout = makeTimeout(100 millis)
|
implicit def timeout = makeTimeout(100 millis)
|
||||||
actorAsk(rendererActor, actorApi.RenderFeaturedJs(game)) onSuccess {
|
actorAsk(rendererActor, actorApi.RenderFeaturedJs(game)) onSuccess {
|
||||||
|
|
|
@ -14,7 +14,7 @@ import User.{ LightPerf, LightCount }
|
||||||
final class Cached(
|
final class Cached(
|
||||||
userColl: Coll,
|
userColl: Coll,
|
||||||
nbTtl: FiniteDuration,
|
nbTtl: FiniteDuration,
|
||||||
onlineUserIdMemo: lila.memo.ExpireSetMemo,
|
onlineUserIds: () => Set[User.ID],
|
||||||
mongoCache: lila.memo.MongoCache.Builder,
|
mongoCache: lila.memo.MongoCache.Builder,
|
||||||
asyncCache: lila.memo.AsyncCache.Builder,
|
asyncCache: lila.memo.AsyncCache.Builder,
|
||||||
rankingApi: RankingApi
|
rankingApi: RankingApi
|
||||||
|
@ -60,7 +60,7 @@ final class Cached(
|
||||||
private val top50OnlineCache = new lila.memo.PeriodicRefreshCache[List[User]](
|
private val top50OnlineCache = new lila.memo.PeriodicRefreshCache[List[User]](
|
||||||
every = Every(30 seconds),
|
every = Every(30 seconds),
|
||||||
atMost = AtMost(30 seconds),
|
atMost = AtMost(30 seconds),
|
||||||
f = () => UserRepo.byIdsSortRatingNoBot(onlineUserIdMemo.keys, 50),
|
f = () => UserRepo.byIdsSortRatingNoBot(onlineUserIds(), 50),
|
||||||
default = Nil,
|
default = Nil,
|
||||||
logger = logger branch "top50online",
|
logger = logger branch "top50online",
|
||||||
initialDelay = 15 seconds
|
initialDelay = 15 seconds
|
||||||
|
|
|
@ -4,8 +4,6 @@ import akka.actor._
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.Config
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
import lila.hub.actorApi.socket.WithUserIds
|
|
||||||
|
|
||||||
final class Env(
|
final class Env(
|
||||||
config: Config,
|
config: Config,
|
||||||
db: lila.db.Env,
|
db: lila.db.Env,
|
||||||
|
@ -13,6 +11,7 @@ final class Env(
|
||||||
asyncCache: lila.memo.AsyncCache.Builder,
|
asyncCache: lila.memo.AsyncCache.Builder,
|
||||||
scheduler: lila.common.Scheduler,
|
scheduler: lila.common.Scheduler,
|
||||||
timeline: ActorSelection,
|
timeline: ActorSelection,
|
||||||
|
onlineUserIds: () => Set[User.ID],
|
||||||
system: ActorSystem
|
system: ActorSystem
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -33,7 +32,6 @@ final class Env(
|
||||||
|
|
||||||
val lightUserApi = new LightUserApi(userColl)(system)
|
val lightUserApi = new LightUserApi(userColl)(system)
|
||||||
|
|
||||||
val onlineUserIdMemo = new lila.memo.ExpireSetMemo(ttl = OnlineTtl)
|
|
||||||
val recentTitledUserIdMemo = new lila.memo.ExpireSetMemo(ttl = 3 hours)
|
val recentTitledUserIdMemo = new lila.memo.ExpireSetMemo(ttl = 3 hours)
|
||||||
|
|
||||||
def isOnline(userId: User.ID): Boolean = onlineUserIdMemo get userId
|
def isOnline(userId: User.ID): Boolean = onlineUserIdMemo get userId
|
||||||
|
@ -83,15 +81,10 @@ final class Env(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
scheduler.effect(3 seconds, "refresh online user ids") {
|
|
||||||
system.lilaBus.publish(WithUserIds(onlineUserIdMemo.putAll), 'socketUsers)
|
|
||||||
onlineUserIdMemo put User.lichessId
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val cached = new Cached(
|
lazy val cached = new Cached(
|
||||||
userColl = userColl,
|
userColl = userColl,
|
||||||
nbTtl = CachedNbTtl,
|
nbTtl = CachedNbTtl,
|
||||||
onlineUserIdMemo = onlineUserIdMemo,
|
onlineUserIds = onlineUserIds,
|
||||||
mongoCache = mongoCache,
|
mongoCache = mongoCache,
|
||||||
asyncCache = asyncCache,
|
asyncCache = asyncCache,
|
||||||
rankingApi = rankingApi
|
rankingApi = rankingApi
|
||||||
|
@ -122,6 +115,7 @@ object Env {
|
||||||
asyncCache = lila.memo.Env.current.asyncCache,
|
asyncCache = lila.memo.Env.current.asyncCache,
|
||||||
scheduler = lila.common.PlayApp.scheduler,
|
scheduler = lila.common.PlayApp.scheduler,
|
||||||
timeline = lila.hub.Env.current.timeline,
|
timeline = lila.hub.Env.current.timeline,
|
||||||
|
onlineUserIds = lila.socket.Env.current.onlineUserIds,
|
||||||
system = lila.common.PlayApp.system
|
system = lila.common.PlayApp.system
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue