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"
|
||||
goodUser <- !user.lameOrTroll ?? { !Env.playban.api.hasCurrentBan(user.id) }
|
||||
_ <- lila.user.UserRepo.disable(user, keepEmail = !goodUser)
|
||||
_ = Env.user.onlineUserIdMemo.remove(user.id)
|
||||
_ = Env.user.recentTitledUserIdMemo.remove(user.id)
|
||||
_ <- !goodUser ?? Env.relation.api.fetchFollowing(user.id) flatMap {
|
||||
Env.activity.write.unfollowAll(user, _)
|
||||
|
|
|
@ -4,7 +4,7 @@ import play.api.http._
|
|||
import play.api.mvc.Codec
|
||||
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] =
|
||||
ContentTypeOf[Frag](Some(ContentTypes.HTML))
|
||||
|
|
|
@ -4,9 +4,6 @@ import akka.actor._
|
|||
import java.lang.management.ManagementFactory
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.hub.actorApi.round.NbRounds
|
||||
import lila.socket.actorApi.NbMembers
|
||||
|
||||
private final class KamonPusher(
|
||||
countUsers: () => Int
|
||||
) extends Actor {
|
||||
|
@ -25,12 +22,6 @@ private final class KamonPusher(
|
|||
|
||||
def receive = {
|
||||
|
||||
case NbMembers(nb) =>
|
||||
lila.mon.socket.count.all(nb)
|
||||
|
||||
case NbRounds(nb) =>
|
||||
lila.mon.round.actor.count(nb)
|
||||
|
||||
case Tick =>
|
||||
lila.mon.jvm.thread(threadStats.getThreadCount)
|
||||
lila.mon.jvm.daemon(threadStats.getDaemonThreadCount)
|
||||
|
@ -45,5 +36,5 @@ object KamonPusher {
|
|||
private case object Tick
|
||||
|
||||
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
|
||||
|
||||
final class BotPlayer(
|
||||
chatActor: ActorSelection,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
isOfferingRematch: Pov => Boolean
|
||||
)(implicit system: ActorSystem) {
|
||||
|
||||
|
@ -44,7 +44,7 @@ final class BotPlayer(
|
|||
val source = d.room == "spectator" option {
|
||||
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)
|
||||
|
|
|
@ -6,7 +6,7 @@ import lila.game.{ Game, Pov }
|
|||
|
||||
final class Env(
|
||||
system: ActorSystem,
|
||||
hub: lila.hub.Env,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
onlineUserIds: lila.memo.ExpireSetMemo,
|
||||
lightUserApi: lila.user.LightUserApi,
|
||||
rematchOf: Game.ID => Option[Game.ID],
|
||||
|
@ -17,7 +17,7 @@ final class Env(
|
|||
|
||||
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
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ object Env {
|
|||
|
||||
lazy val current: Env = "bot" boot new Env(
|
||||
system = lila.common.PlayApp.system,
|
||||
hub = lila.hub.Env.current,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
onlineUserIds = lila.user.Env.current.onlineUserIdMemo,
|
||||
lightUserApi = lila.user.Env.current.lightUserApi,
|
||||
rematchOf = lila.game.Env.current.rematches.getIfPresent,
|
||||
|
|
|
@ -2,9 +2,7 @@ package lila
|
|||
|
||||
import org.joda.time.DateTime
|
||||
|
||||
import lila.socket.WithSocket
|
||||
|
||||
package object challenge extends PackageObject with WithSocket {
|
||||
package object challenge extends PackageObject {
|
||||
|
||||
type EitherChallenger = Either[Challenge.Anonymous, Challenge.Registered]
|
||||
|
||||
|
|
|
@ -196,9 +196,9 @@ final class ChatApi(
|
|||
private def publish(chatId: Chat.Id, msg: Any): Unit =
|
||||
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(
|
||||
$id(chatId),
|
||||
|
|
|
@ -45,14 +45,10 @@ final class Env(
|
|||
|
||||
val panic = new ChatPanic
|
||||
|
||||
private val palantir = new Palantir(system.lilaBus)
|
||||
|
||||
system.scheduler.schedule(TimeoutCheckEvery, TimeoutCheckEvery) {
|
||||
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 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 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 Timeout(chatId: Chat.Id, mod: String, userId: String, reason: ChatTimeout.Reason, local: Boolean)
|
||||
|
||||
case class OnTimeout(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")
|
||||
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 {
|
||||
def queueSize(name: String) = rec(s"trouper.queue_size.$name")
|
||||
|
|
|
@ -28,16 +28,12 @@ final class Env(
|
|||
asyncCache = asyncCache
|
||||
)
|
||||
|
||||
lazy val socketHandler = new EvalCacheSocketHandler(
|
||||
private lazy val socketHandler = new EvalCacheSocketHandler(
|
||||
api = api,
|
||||
truster = truster,
|
||||
upgrade = upgrade
|
||||
)
|
||||
|
||||
bus.subscribeFun('socketLeave) {
|
||||
case lila.socket.actorApi.SocketLeave(sri, _) => upgrade unregister sri
|
||||
}
|
||||
|
||||
// remote socket support
|
||||
bus.subscribeFun(Symbol("remoteSocketIn:evalGet")) {
|
||||
case TellSriIn(sri, _, msg) => msg obj "d" foreach { d =>
|
||||
|
|
|
@ -7,7 +7,7 @@ import chess.format.FEN
|
|||
import lila.socket._
|
||||
import lila.user.User
|
||||
|
||||
final class EvalCacheSocketHandler(
|
||||
private final class EvalCacheSocketHandler(
|
||||
api: EvalCacheApi,
|
||||
truster: EvalCacheTruster,
|
||||
upgrade: EvalCacheUpgrade
|
||||
|
@ -15,23 +15,7 @@ final class EvalCacheSocketHandler(
|
|||
|
||||
import EvalCacheEntry._
|
||||
|
||||
def apply(sri: Socket.Sri, member: SocketMember, user: Option[User]): Handler.Controller =
|
||||
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(
|
||||
def evalGet(
|
||||
sri: Socket.Sri,
|
||||
d: JsObject,
|
||||
push: JsObject => Unit
|
||||
|
@ -50,7 +34,7 @@ final class EvalCacheSocketHandler(
|
|||
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 {
|
||||
_ foreach { tu =>
|
||||
JsonHandlers.readPutData(tu, data) foreach {
|
||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
|||
|
||||
import chess.format.FEN
|
||||
import chess.variant.Variant
|
||||
import lila.socket.{ Socket, SocketMember }
|
||||
import lila.socket.Socket
|
||||
import lila.memo.ExpireCallbackMemo
|
||||
|
||||
/* 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 shutup = select("actor.shutup")
|
||||
val mod = select("actor.mod")
|
||||
val chat = select("actor.chat")
|
||||
val notification = select("actor.notify")
|
||||
|
||||
val bus = system.lilaBus
|
||||
|
||||
private def select(name: String) =
|
||||
system actorSelection ("/user/" + config.getString(name))
|
||||
system.actorSelection("/user/" + config.getString(name))
|
||||
}
|
||||
|
||||
object Env {
|
||||
|
|
|
@ -27,8 +27,6 @@ package map {
|
|||
}
|
||||
|
||||
package socket {
|
||||
case class WithUserIds(f: Iterable[String] => Unit)
|
||||
case class HasUserId(userId: String, promise: Promise[Boolean])
|
||||
case class SendTo(userId: String, message: JsObject)
|
||||
object SendTo {
|
||||
def apply[A: Writes](userId: String, typ: String, data: A): SendTo =
|
||||
|
@ -242,7 +240,6 @@ package round {
|
|||
simulId: String,
|
||||
opponentUserId: String
|
||||
)
|
||||
case class NbRounds(nb: Int)
|
||||
case class Berserk(gameId: String, userId: String)
|
||||
case class IsOnGame(color: chess.Color, promise: Promise[Boolean])
|
||||
case class TourStandingOld(data: JsArray)
|
||||
|
|
|
@ -27,6 +27,7 @@ final class LobbySocket(
|
|||
|
||||
import LobbySocket._
|
||||
import Protocol._
|
||||
type SocketController = PartialFunction[(String, JsObject), Unit]
|
||||
|
||||
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) =
|
||||
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") {
|
||||
o str "d" foreach { id =>
|
||||
lobby ! BiteHook(id, member.sri, member.user)
|
||||
|
@ -231,7 +232,7 @@ final class LobbySocket(
|
|||
getOrConnect(sri, user) foreach { member =>
|
||||
controller(member).applyOrElse(tpe -> msg, {
|
||||
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)
|
||||
|
||||
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 {
|
||||
|
@ -260,8 +256,6 @@ private object LobbySocket {
|
|||
|
||||
object Protocol {
|
||||
object Out {
|
||||
def nbMembers(nb: Int) = s"member/nb $nb"
|
||||
def nbRounds(nb: Int) = s"round/nb $nb"
|
||||
def pairings(pairings: List[PoolApi.Pairing]) = {
|
||||
val redirs = for {
|
||||
pairing <- pairings
|
||||
|
|
|
@ -6,7 +6,6 @@ import scala.concurrent.Promise
|
|||
|
||||
import lila.game.Game
|
||||
import lila.socket.Socket.{ Sri, Sris }
|
||||
import lila.socket.{ SocketMember, DirectSocketMember, RemoteSocketMember }
|
||||
import lila.user.User
|
||||
|
||||
private[lobby] case class SaveSeek(msg: AddSeek)
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package lila
|
||||
|
||||
import lila.socket.WithSocket
|
||||
|
||||
package object lobby extends PackageObject with WithSocket {
|
||||
package object lobby extends PackageObject {
|
||||
|
||||
private[lobby] def logger = lila.log("lobby")
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package lila.room
|
||||
|
||||
import lila.chat.{ Chat, UserLine, actorApi => chatApi }
|
||||
import lila.chat.{ Chat, ChatApi, UserLine, actorApi => chatApi }
|
||||
import lila.common.Bus
|
||||
import lila.hub.actorApi.shutup.PublicSource
|
||||
import lila.hub.{ Trouper, TrouperMap }
|
||||
|
@ -62,16 +62,16 @@ object RoomSocket {
|
|||
|
||||
def roomHandler(
|
||||
rooms: TrouperMap[RoomState],
|
||||
chat: akka.actor.ActorSelection,
|
||||
chat: ChatApi,
|
||||
logger: Logger,
|
||||
publicSource: RoomId => PublicSource.type => Option[PublicSource],
|
||||
localTimeout: Option[(RoomId, User.ID, User.ID) => Fu[Boolean]] = None
|
||||
): Handler = ({
|
||||
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 =>
|
||||
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)
|
||||
|
|
|
@ -11,8 +11,6 @@ import actorApi.{ GetSocketStatus, SocketStatus }
|
|||
import lila.game.{ Game, GameRepo, Pov, PlayerRef }
|
||||
import lila.hub.actorApi.map.Tell
|
||||
import lila.hub.actorApi.round.{ Abort, Resign, FishnetPlay }
|
||||
import lila.hub.actorApi.socket.HasUserId
|
||||
import lila.hub.actorApi.{ Announce, DeployPost }
|
||||
import lila.user.User
|
||||
|
||||
final class Env(
|
||||
|
@ -20,6 +18,7 @@ final class Env(
|
|||
system: ActorSystem,
|
||||
db: lila.db.Env,
|
||||
hub: lila.hub.Env,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
fishnetPlayer: lila.fishnet.Player,
|
||||
aiPerfApi: lila.fishnet.AiPerfApi,
|
||||
crosstableApi: lila.game.CrosstableApi,
|
||||
|
@ -35,12 +34,10 @@ final class Env(
|
|||
prefApi: lila.pref.PrefApi,
|
||||
historyApi: lila.history.HistoryApi,
|
||||
evalCache: lila.evalCache.EvalCacheApi,
|
||||
evalCacheHandler: lila.evalCache.EvalCacheSocketHandler,
|
||||
remoteSocketApi: lila.socket.RemoteSocket,
|
||||
isBotSync: lila.common.LightUser.IsBotSync,
|
||||
slackApi: lila.slack.SlackApi,
|
||||
ratingFactors: () => lila.rating.RatingFactors,
|
||||
settingStore: lila.memo.SettingStore.Builder
|
||||
ratingFactors: () => lila.rating.RatingFactors
|
||||
) {
|
||||
|
||||
private val settings = new {
|
||||
|
@ -59,9 +56,6 @@ final class Env(
|
|||
|
||||
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 defaultGoneWeight = fuccess(1f)
|
||||
|
@ -115,10 +109,6 @@ final class Env(
|
|||
private var nbRounds = 0
|
||||
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)
|
||||
|
||||
object proxy {
|
||||
|
@ -228,15 +218,13 @@ final class Env(
|
|||
bus = bus
|
||||
)
|
||||
|
||||
lazy val messenger = new Messenger(
|
||||
chat = hub.chat
|
||||
)
|
||||
lazy val messenger = new Messenger(chatApi)
|
||||
|
||||
def getSocketStatus(game: Game): Fu[SocketStatus] =
|
||||
roundSocket.rounds.ask[SocketStatus](game.id)(GetSocketStatus)
|
||||
|
||||
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(
|
||||
noteApi = noteApi,
|
||||
|
@ -263,10 +251,10 @@ final class Env(
|
|||
}
|
||||
}
|
||||
|
||||
MoveMonitor.start(system, moveTimeChannel)
|
||||
MoveMonitor.start(system)
|
||||
|
||||
system.actorOf(
|
||||
Props(new Titivate(tellRound, hub.bookmark, hub.chat)),
|
||||
Props(new Titivate(tellRound, hub.bookmark, chatApi)),
|
||||
name = "titivate"
|
||||
)
|
||||
|
||||
|
@ -303,6 +291,7 @@ object Env {
|
|||
system = lila.common.PlayApp.system,
|
||||
db = lila.db.Env.current,
|
||||
hub = lila.hub.Env.current,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
fishnetPlayer = lila.fishnet.Env.current.player,
|
||||
aiPerfApi = lila.fishnet.Env.current.aiPerfApi,
|
||||
crosstableApi = lila.game.Env.current.crosstableApi,
|
||||
|
@ -318,11 +307,9 @@ object Env {
|
|||
prefApi = lila.pref.Env.current.api,
|
||||
historyApi = lila.history.Env.current.api,
|
||||
evalCache = lila.evalCache.Env.current.api,
|
||||
evalCacheHandler = lila.evalCache.Env.current.socketHandler,
|
||||
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
||||
isBotSync = lila.user.Env.current.lightUserApi.isBotSync,
|
||||
slackApi = lila.slack.Env.current.api,
|
||||
ratingFactors = lila.rating.Env.current.ratingFactorsSetting.get,
|
||||
settingStore = lila.memo.Env.current.settingStore
|
||||
ratingFactors = lila.rating.Env.current.ratingFactorsSetting.get
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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 lila.chat.actorApi._
|
||||
import lila.chat.{ Chat, ChatTimeout }
|
||||
import lila.chat.{ Chat, ChatApi, ChatTimeout }
|
||||
import lila.game.Game
|
||||
import lila.hub.actorApi.shutup.PublicSource
|
||||
import lila.i18n.I18nKey.{ Select => SelectI18nKey }
|
||||
import lila.i18n.{ I18nKeys, enLang }
|
||||
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 = {
|
||||
val translated = message(I18nKeys).literalTxtTo(enLang, args)
|
||||
chat ! SystemTalk(watcherId(Chat.Id(game.id)), translated)
|
||||
if (game.nonAi) chat ! SystemTalk(Chat.Id(game.id), translated)
|
||||
api.userChat.system(watcherId(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 = {
|
||||
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) =
|
||||
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 ")
|
||||
|
||||
|
@ -33,22 +33,22 @@ final class Messenger(val chat: ActorSelection) {
|
|||
whisperCommands.collectFirst {
|
||||
case command if text startsWith command =>
|
||||
val source = PublicSource.Watcher(chatId.value)
|
||||
UserTalk(watcherId(chatId), userId, text drop command.size, source.some)
|
||||
}.orElse {
|
||||
if (text startsWith "/") none // mistyped command?
|
||||
else UserTalk(chatId, userId, text, publicSource = none).some
|
||||
} foreach chat.!
|
||||
api.userChat.write(watcherId(chatId), userId, text drop command.size, source.some)
|
||||
} getOrElse {
|
||||
if (!text.startsWith("/")) // mistyped command?
|
||||
api.userChat.write(chatId, userId, text, publicSource = none).some
|
||||
}
|
||||
|
||||
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
|
||||
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 =
|
||||
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")
|
||||
|
|
|
@ -7,15 +7,11 @@ import scala.concurrent.duration._
|
|||
|
||||
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 {
|
||||
var currentMicros: Int = 0
|
||||
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)
|
||||
}
|
||||
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 org.joda.time.DateTime
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.Promise
|
||||
import ornicar.scalalib.Zero
|
||||
|
||||
import actorApi._, round._
|
||||
|
@ -15,7 +16,6 @@ import lila.hub.actorApi.DeployPost
|
|||
import lila.hub.actorApi.map._
|
||||
import lila.hub.actorApi.round.{ FishnetPlay, FishnetStart, BotPlay, RematchYes, RematchNo, Abort, Resign, IsOnGame }
|
||||
import lila.hub.actorApi.simul.GetHostIds
|
||||
import lila.hub.actorApi.socket.HasUserId
|
||||
import lila.hub.Duct
|
||||
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
||||
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
||||
|
@ -478,6 +478,7 @@ private[round] final class RoundDuct(
|
|||
|
||||
object RoundDuct {
|
||||
|
||||
case class HasUserId(userId: User.ID, promise: Promise[Boolean])
|
||||
case class SetGameInfo(game: lila.game.Game, goneWeights: (Float, Float))
|
||||
case object Tick
|
||||
case object Stop
|
||||
|
|
|
@ -18,7 +18,7 @@ import lila.round.actorApi.round.{ QuietFlag, Abandon }
|
|||
private[round] final class Titivate(
|
||||
tellRound: TellRound,
|
||||
bookmark: ActorSelection,
|
||||
chat: ActorSelection
|
||||
chatApi: lila.chat.ChatApi
|
||||
) extends Actor {
|
||||
|
||||
object Run
|
||||
|
@ -59,7 +59,7 @@ private[round] final class Titivate(
|
|||
|
||||
else if (game.unplayed) {
|
||||
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
|
||||
} 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.socket.Socket.{ SocketVersion, Sri }
|
||||
import lila.socket.DirectSocketMember
|
||||
import lila.user.User
|
||||
import lila.game.Game.{ FullId, PlayerId }
|
||||
|
||||
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 Connected(enumerator: JsEnumerator, member: Member)
|
||||
case class Bye(color: Color)
|
||||
case class ByePlayer(playerId: PlayerId)
|
||||
case class IsGone(color: Color, promise: Promise[Boolean])
|
||||
case class GetSocketStatus(promise: Promise[SocketStatus])
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package lila
|
||||
|
||||
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]
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package lila
|
||||
|
||||
import lila.socket.WithSocket
|
||||
package object setup extends PackageObject {
|
||||
|
||||
package object setup extends PackageObject with WithSocket {
|
||||
|
||||
private[setup] def logger = lila.log("setup")
|
||||
private[setup] val logger = lila.log("setup")
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import scala.concurrent.duration._
|
|||
|
||||
import lila.game.Game
|
||||
import lila.hub.{ Duct, DuctMap }
|
||||
import lila.socket.History
|
||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||
|
||||
final class Env(
|
||||
|
@ -15,6 +14,7 @@ final class Env(
|
|||
scheduler: lila.common.Scheduler,
|
||||
db: lila.db.Env,
|
||||
hub: lila.hub.Env,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
lightUser: lila.common.LightUser.Getter,
|
||||
onGameStart: String => Unit,
|
||||
isOnline: String => Boolean,
|
||||
|
@ -49,7 +49,7 @@ final class Env(
|
|||
getSimul = repo.find,
|
||||
jsonView = jsonView,
|
||||
remoteSocketApi = remoteSocketApi,
|
||||
chat = hub.chat,
|
||||
chat = chatApi,
|
||||
bus = system.lilaBus
|
||||
)
|
||||
|
||||
|
@ -122,6 +122,7 @@ object Env {
|
|||
scheduler = lila.common.PlayApp.scheduler,
|
||||
db = lila.db.Env.current,
|
||||
hub = lila.hub.Env.current,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
lightUser = lila.user.Env.current.lightUser,
|
||||
onGameStart = lila.round.Env.current.onStart,
|
||||
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.timeline.{ Propagate, SimulCreate, SimulJoin }
|
||||
import lila.hub.{ Duct, DuctMap }
|
||||
import lila.socket.actorApi.SendToFlag
|
||||
import lila.socket.Socket.SendToFlag
|
||||
import lila.user.{ User, UserRepo }
|
||||
import makeTimeout.short
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package lila.simul
|
||||
|
||||
import akka.actor.ActorSelection
|
||||
import play.api.libs.json._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
|
@ -14,7 +13,7 @@ private final class SimulSocket(
|
|||
getSimul: Simul.ID => Fu[Option[Simul]],
|
||||
jsonView: JsonView,
|
||||
remoteSocketApi: lila.socket.RemoteSocket,
|
||||
chat: ActorSelection,
|
||||
chat: lila.chat.ChatApi,
|
||||
bus: lila.common.Bus
|
||||
) {
|
||||
|
||||
|
|
|
@ -5,38 +5,14 @@ import scala.concurrent.Promise
|
|||
|
||||
import lila.game.Game
|
||||
import lila.socket.Socket.{ Sri, SocketVersion }
|
||||
import lila.socket.DirectSocketMember
|
||||
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 StartGame(game: Game, hostId: String)
|
||||
private[simul] case class StartSimul(firstGame: Game, hostId: String)
|
||||
private[simul] case class HostIsOn(gameId: String)
|
||||
private[simul] case object Reload
|
||||
private[simul] case object Aborted
|
||||
private[simul] case class Connected(enumerator: JsEnumerator, member: SimulSocketMember)
|
||||
|
||||
private[simul] case object NotifyCrowd
|
||||
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package lila
|
||||
|
||||
import lila.socket.WithSocket
|
||||
|
||||
package object simul extends PackageObject with WithSocket {
|
||||
package object simul extends PackageObject {
|
||||
|
||||
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 scala.concurrent.duration._
|
||||
|
||||
import actorApi._
|
||||
|
||||
final class Env(
|
||||
system: ActorSystem,
|
||||
config: Config,
|
||||
|
@ -16,22 +14,15 @@ final class Env(
|
|||
|
||||
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(
|
||||
redisClient = RedisClient create RedisURI.create(RedisUri),
|
||||
notificationActor = hub.notification,
|
||||
setNb = nb => population ! actorApi.RemoteNbMembers(nb),
|
||||
bus = system.lilaBus,
|
||||
lifecycle = lifecycle
|
||||
)
|
||||
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 {
|
||||
|
|
|
@ -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.security.CloseAccount
|
||||
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.{ TrouperMap, Trouper }
|
||||
import lila.socket.actorApi.SendToFlag
|
||||
import Socket.{ SocketVersion, GetVersion, Sri }
|
||||
import Socket.{ SocketVersion, GetVersion, Sri, SendToFlag }
|
||||
|
||||
final class RemoteSocket(
|
||||
redisClient: RedisClient,
|
||||
notificationActor: akka.actor.ActorSelection,
|
||||
setNb: Int => Unit,
|
||||
bus: lila.common.Bus,
|
||||
lifecycle: play.api.inject.ApplicationLifecycle
|
||||
) {
|
||||
|
@ -43,32 +41,31 @@ final class RemoteSocket(
|
|||
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]
|
||||
|
||||
val baseHandler: Handler = {
|
||||
case In.ConnectUser(userId) =>
|
||||
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) =>
|
||||
connectedUserIds.getAndUpdate((x: UserIds) => x -- userIds)
|
||||
onlineUserIds.getAndUpdate((x: UserIds) => x -- userIds)
|
||||
case In.Watch(gameId) => watchedGameIds += gameId
|
||||
case In.Unwatch(gameId) => watchedGameIds -= gameId
|
||||
case In.NotifiedBatch(userIds) => notificationActor ! lila.hub.actorApi.notify.NotifiedBatch(userIds)
|
||||
case In.Connections(nb) => tick(nb)
|
||||
case In.FriendsBatch(userIds) => userIds foreach { userId =>
|
||||
bus.publish(ReloadOnlineFriends(userId), 'reloadOnlineFriends)
|
||||
}
|
||||
case In.Lags(lags) =>
|
||||
lags foreach (UserLagCache.put _).tupled
|
||||
// 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) =>
|
||||
bus.publish(TellSriIn(sri.value, userId, msg), Symbol(s"remoteSocketIn:$typ"))
|
||||
case In.WsBoot =>
|
||||
logger.warn("Remote socket boot")
|
||||
connectedUserIds set Set.empty
|
||||
onlineUserIds set Set("lichess")
|
||||
watchedGameIds.clear
|
||||
case In.ReqResponse(reqId, response) =>
|
||||
requests.computeIfPresent(reqId, (_: Int, promise: Promise[String]) => {
|
||||
|
@ -81,36 +78,26 @@ final class RemoteSocket(
|
|||
case MoveEvent(gameId, fen, move) =>
|
||||
if (watchedGameIds(gameId)) send(Out.move(gameId, move, fen))
|
||||
case SendTos(userIds, payload) =>
|
||||
val connectedUsers = userIds intersect connectedUserIds.get
|
||||
val connectedUsers = userIds intersect onlineUserIds.get
|
||||
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))
|
||||
case Announce(_, _, json) =>
|
||||
send(Out.tellAll(Json.obj("t" -> "announce", "d" -> json)))
|
||||
case Mlat(micros) =>
|
||||
send(Out.mlat(micros))
|
||||
case actorApi.SendToFlag(flag, payload) =>
|
||||
case Socket.SendToFlag(flag, payload) =>
|
||||
send(Out.tellFlag(flag, payload))
|
||||
case TellSriOut(sri, payload) =>
|
||||
send(Out.tellSri(Sri(sri), payload))
|
||||
case CloseAccount(userId) =>
|
||||
send(Out.disconnectUser(userId))
|
||||
case WithUserIds(f) =>
|
||||
f(connectedUserIds.get)
|
||||
case lila.hub.actorApi.mod.Shadowban(userId, v) =>
|
||||
send(Out.setTroll(userId, v))
|
||||
case lila.hub.actorApi.mod.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)
|
||||
|
||||
private val send: Send = makeSender("site-out").apply _
|
||||
|
@ -141,6 +128,8 @@ final class RemoteSocket(
|
|||
|
||||
object RemoteSocket {
|
||||
|
||||
private val logger = lila log "socket"
|
||||
|
||||
type Send = String => Unit
|
||||
|
||||
final class Sender(conn: StatefulRedisPubSubConnection[String, String], channel: Channel) {
|
||||
|
@ -173,7 +162,6 @@ object RemoteSocket {
|
|||
case class Watch(gameId: String) extends In
|
||||
case class Unwatch(gameId: 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 Lags(lags: Map[String, Centis]) extends In
|
||||
case class FriendsBatch(userIds: Iterable[String]) extends In
|
||||
|
@ -192,7 +180,6 @@ object RemoteSocket {
|
|||
case "watch" => Watch(raw.args).some
|
||||
case "unwatch" => Unwatch(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 "lags" => Lags(commas(raw.args).flatMap {
|
||||
_ split ':' match {
|
||||
|
|
|
@ -24,6 +24,8 @@ object Socket extends Socket {
|
|||
|
||||
case class GetVersion(promise: Promise[SocketVersion])
|
||||
|
||||
case class SendToFlag(flag: String, message: JsObject)
|
||||
|
||||
val initialPong = makeMessage("n")
|
||||
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._
|
||||
|
||||
object UserLagCache {
|
||||
|
||||
private val cache: Cache[String, Centis] = Scaffeine()
|
||||
.expireAfterWrite(15 minutes)
|
||||
.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 object socket extends PackageObject with socket.WithSocket {
|
||||
|
||||
val logger = lila.log("socket")
|
||||
}
|
||||
package object socket extends PackageObject
|
||||
|
|
|
@ -3,7 +3,7 @@ package lila.study
|
|||
import chess.format.pgn.Tags
|
||||
import chess.format.{ Forsyth, FEN }
|
||||
import chess.variant.{ Variant, Crazyhouse }
|
||||
import lila.chat.Chat
|
||||
import lila.chat.{ Chat, ChatApi }
|
||||
import lila.game.{ Game, GameRepo, Namer }
|
||||
import lila.importer.Importer
|
||||
import lila.user.User
|
||||
|
@ -11,7 +11,7 @@ import lila.user.User
|
|||
private final class ChapterMaker(
|
||||
domain: String,
|
||||
lightUser: lila.user.LightUserApi,
|
||||
chat: akka.actor.ActorSelection,
|
||||
chatApi: ChatApi,
|
||||
importer: Importer,
|
||||
pgnFetch: PgnFetch,
|
||||
pgnDump: lila.game.PgnDump
|
||||
|
@ -138,7 +138,7 @@ private final class ChapterMaker(
|
|||
|
||||
def notifyChat(study: Study, game: Game, userId: User.ID) =
|
||||
if (study.isPublic) List(game.id, s"${game.id}/w") foreach { chatId =>
|
||||
chat ! lila.chat.actorApi.UserTalk(
|
||||
chatApi.userChat.write(
|
||||
chatId = Chat.Id(chatId),
|
||||
userId = userId,
|
||||
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 scala.concurrent.duration._
|
||||
|
||||
import lila.hub.actorApi.socket.HasUserId
|
||||
import lila.hub.{ Duct, DuctMap }
|
||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||
import lila.user.User
|
||||
|
@ -16,13 +15,13 @@ final class Env(
|
|||
divider: lila.game.Divider,
|
||||
importer: lila.importer.Importer,
|
||||
explorerImporter: lila.explorer.ExplorerImporter,
|
||||
evalCacheHandler: lila.evalCache.EvalCacheSocketHandler,
|
||||
notifyApi: lila.notify.NotifyApi,
|
||||
getPref: User => Fu[lila.pref.Pref],
|
||||
getRelation: (User.ID, User.ID) => Fu[Option[lila.relation.Relation]],
|
||||
remoteSocketApi: lila.socket.RemoteSocket,
|
||||
system: ActorSystem,
|
||||
hub: lila.hub.Env,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
db: lila.db.Env,
|
||||
asyncCache: lila.memo.AsyncCache.Builder
|
||||
) {
|
||||
|
@ -48,7 +47,7 @@ final class Env(
|
|||
jsonView = jsonView,
|
||||
lightStudyCache = lightStudyCache,
|
||||
remoteSocketApi = remoteSocketApi,
|
||||
chat = hub.chat,
|
||||
chatApi = chatApi,
|
||||
bus = system.lilaBus
|
||||
)
|
||||
|
||||
|
@ -67,7 +66,7 @@ final class Env(
|
|||
pgnFetch = new PgnFetch,
|
||||
pgnDump = gamePgnDump,
|
||||
lightUser = lightUserApi,
|
||||
chat = hub.chat,
|
||||
chatApi = chatApi,
|
||||
domain = NetDomain
|
||||
)
|
||||
|
||||
|
@ -121,7 +120,7 @@ final class Env(
|
|||
explorerGameHandler = explorerGame,
|
||||
lightUser = lightUserApi.sync,
|
||||
scheduler = system.scheduler,
|
||||
chat = hub.chat,
|
||||
chatApi = chatApi,
|
||||
bus = system.lilaBus,
|
||||
timeline = hub.timeline,
|
||||
serverEvalRequester = serverEvalRequester,
|
||||
|
@ -174,13 +173,13 @@ object Env {
|
|||
divider = lila.game.Env.current.divider,
|
||||
importer = lila.importer.Env.current.importer,
|
||||
explorerImporter = lila.explorer.Env.current.importer,
|
||||
evalCacheHandler = lila.evalCache.Env.current.socketHandler,
|
||||
notifyApi = lila.notify.Env.current.api,
|
||||
getPref = lila.pref.Env.current.api.getPref,
|
||||
getRelation = lila.relation.Env.current.api.fetchRelation,
|
||||
remoteSocketApi = lila.socket.Env.current.remoteSocket,
|
||||
system = lila.common.PlayApp.system,
|
||||
hub = lila.hub.Env.current,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
db = lila.db.Env.current,
|
||||
asyncCache = lila.memo.Env.current.asyncCache
|
||||
)
|
||||
|
|
|
@ -6,7 +6,7 @@ import scala.concurrent.duration._
|
|||
import actorApi.Who
|
||||
import chess.Centis
|
||||
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.timeline.{ Propagate, StudyCreate, StudyLike }
|
||||
import lila.socket.Socket.Sri
|
||||
|
@ -24,7 +24,7 @@ final class StudyApi(
|
|||
explorerGameHandler: ExplorerGame,
|
||||
lightUser: lila.common.LightUser.GetterSync,
|
||||
scheduler: akka.actor.Scheduler,
|
||||
chat: ActorSelection,
|
||||
chatApi: ChatApi,
|
||||
bus: lila.common.Bus,
|
||||
timeline: ActorSelection,
|
||||
serverEvalRequester: ServerEval.Requester,
|
||||
|
@ -127,7 +127,7 @@ final class StudyApi(
|
|||
newChapters.headOption.map(study1.rewindTo) ?? { study =>
|
||||
studyRepo.insert(study) >>
|
||||
newChapters.map(chapterRepo.insert).sequenceFu >>- {
|
||||
chat ! lila.chat.actorApi.SystemTalk(
|
||||
chatApi.userChat.system(
|
||||
Chat.Id(study.id.value),
|
||||
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 {
|
||||
_ foreach { study =>
|
||||
(study canChat userId) ?? {
|
||||
chat ! lila.chat.actorApi.UserTalk(
|
||||
chatApi.userChat.write(
|
||||
Chat.Id(studyId.value),
|
||||
userId = userId,
|
||||
text = text,
|
||||
|
@ -719,7 +719,7 @@ final class StudyApi(
|
|||
}
|
||||
|
||||
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) >>
|
||||
chapterRepo.deleteByStudyIds(ids)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import akka.actor._
|
|||
import akka.pattern.ask
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.hub.actorApi.socket.HasUserId
|
||||
import lila.notify.{ InvitedToStudy, NotifyApi, Notification }
|
||||
import lila.pref.Pref
|
||||
import lila.relation.{ Block, Follow }
|
||||
|
|
|
@ -11,7 +11,7 @@ import chess.format.pgn.{ Glyph, Glyphs }
|
|||
import lila.room.RoomSocket.{ Protocol => RP, _ }
|
||||
import lila.socket.RemoteSocket.{ Protocol => P, _ }
|
||||
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.user.User
|
||||
|
||||
|
@ -20,7 +20,7 @@ private final class StudySocket(
|
|||
jsonView: JsonView,
|
||||
lightStudyCache: LightStudyCache,
|
||||
remoteSocketApi: lila.socket.RemoteSocket,
|
||||
chat: ActorSelection,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
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
|
||||
localTimeout = Some { (roomId, modId, suspectId) =>
|
||||
api.isContributor(roomId, modId) >>& !api.isMember(roomId, suspectId)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package lila
|
||||
|
||||
import lila.socket.WithSocket
|
||||
import lila.hub.TrouperMap
|
||||
|
||||
package object study extends PackageObject with WithSocket {
|
||||
package object study extends PackageObject {
|
||||
|
||||
private[study] val logger = lila.log("study")
|
||||
|
||||
|
|
|
@ -7,10 +7,8 @@ import scala.concurrent.Promise
|
|||
|
||||
import lila.game.Game
|
||||
import lila.hub.{ Duct, DuctMap, TrouperMap }
|
||||
import lila.socket.History
|
||||
import lila.socket.Socket.{ GetVersion, SocketVersion }
|
||||
import lila.user.User
|
||||
import makeTimeout.short
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
|
@ -21,6 +19,7 @@ final class Env(
|
|||
proxyGame: Game.ID => Fu[Option[Game]],
|
||||
flood: lila.security.Flood,
|
||||
hub: lila.hub.Env,
|
||||
chatApi: lila.chat.ChatApi,
|
||||
tellRound: lila.round.TellRound,
|
||||
lightUserApi: lila.user.LightUserApi,
|
||||
isOnline: User.ID => Boolean,
|
||||
|
@ -85,7 +84,7 @@ final class Env(
|
|||
|
||||
private val socket = new TournamentSocket(
|
||||
remoteSocketApi = remoteSocketApi,
|
||||
chat = hub.chat,
|
||||
chat = chatApi,
|
||||
system = system
|
||||
)
|
||||
|
||||
|
@ -193,6 +192,7 @@ object Env {
|
|||
proxyGame = lila.round.Env.current.proxy.game _,
|
||||
flood = lila.security.Env.current.flood,
|
||||
hub = lila.hub.Env.current,
|
||||
chatApi = lila.chat.Env.current.api,
|
||||
tellRound = lila.round.Env.current.tellRound,
|
||||
lightUserApi = lila.user.Env.current.lightUserApi,
|
||||
isOnline = lila.user.Env.current.isOnline,
|
||||
|
|
|
@ -16,7 +16,7 @@ import lila.hub.actorApi.timeline.{ Propagate, TourJoin }
|
|||
import lila.hub.lightTeam._
|
||||
import lila.hub.{ Duct, DuctMap }
|
||||
import lila.round.actorApi.round.{ GoBerserk, AbortForce }
|
||||
import lila.socket.actorApi.SendToFlag
|
||||
import lila.socket.Socket.SendToFlag
|
||||
import lila.user.{ User, UserRepo }
|
||||
import makeTimeout.short
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import lila.user.User
|
|||
|
||||
private final class TournamentSocket(
|
||||
remoteSocketApi: lila.socket.RemoteSocket,
|
||||
chat: ActorSelection,
|
||||
chat: lila.chat.ChatApi,
|
||||
system: ActorSystem
|
||||
) {
|
||||
|
||||
|
|
|
@ -22,13 +22,9 @@ final class Env(
|
|||
|
||||
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(
|
||||
system,
|
||||
hub.renderer,
|
||||
selectChannel,
|
||||
lightUser,
|
||||
onSelect,
|
||||
proxyGame,
|
||||
|
|
|
@ -13,7 +13,6 @@ import lila.hub.Trouper
|
|||
private[tv] final class TvTrouper(
|
||||
system: ActorSystem,
|
||||
rendererActor: ActorSelection,
|
||||
selectChannel: lila.socket.Channel,
|
||||
lightUser: LightUser.GetterSync,
|
||||
onSelect: Game => Unit,
|
||||
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) // new rounds
|
||||
system.lilaBus.publish(lila.hub.actorApi.tv.TvSelect(game.id, game.speed, data), 'tvSelect)
|
||||
if (channel == Tv.Channel.Best) {
|
||||
implicit def timeout = makeTimeout(100 millis)
|
||||
actorAsk(rendererActor, actorApi.RenderFeaturedJs(game)) onSuccess {
|
||||
|
|
|
@ -14,7 +14,7 @@ import User.{ LightPerf, LightCount }
|
|||
final class Cached(
|
||||
userColl: Coll,
|
||||
nbTtl: FiniteDuration,
|
||||
onlineUserIdMemo: lila.memo.ExpireSetMemo,
|
||||
onlineUserIds: () => Set[User.ID],
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
rankingApi: RankingApi
|
||||
|
@ -60,7 +60,7 @@ final class Cached(
|
|||
private val top50OnlineCache = new lila.memo.PeriodicRefreshCache[List[User]](
|
||||
every = Every(30 seconds),
|
||||
atMost = AtMost(30 seconds),
|
||||
f = () => UserRepo.byIdsSortRatingNoBot(onlineUserIdMemo.keys, 50),
|
||||
f = () => UserRepo.byIdsSortRatingNoBot(onlineUserIds(), 50),
|
||||
default = Nil,
|
||||
logger = logger branch "top50online",
|
||||
initialDelay = 15 seconds
|
||||
|
|
|
@ -4,8 +4,6 @@ import akka.actor._
|
|||
import com.typesafe.config.Config
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.hub.actorApi.socket.WithUserIds
|
||||
|
||||
final class Env(
|
||||
config: Config,
|
||||
db: lila.db.Env,
|
||||
|
@ -13,6 +11,7 @@ final class Env(
|
|||
asyncCache: lila.memo.AsyncCache.Builder,
|
||||
scheduler: lila.common.Scheduler,
|
||||
timeline: ActorSelection,
|
||||
onlineUserIds: () => Set[User.ID],
|
||||
system: ActorSystem
|
||||
) {
|
||||
|
||||
|
@ -33,7 +32,6 @@ final class Env(
|
|||
|
||||
val lightUserApi = new LightUserApi(userColl)(system)
|
||||
|
||||
val onlineUserIdMemo = new lila.memo.ExpireSetMemo(ttl = OnlineTtl)
|
||||
val recentTitledUserIdMemo = new lila.memo.ExpireSetMemo(ttl = 3 hours)
|
||||
|
||||
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(
|
||||
userColl = userColl,
|
||||
nbTtl = CachedNbTtl,
|
||||
onlineUserIdMemo = onlineUserIdMemo,
|
||||
onlineUserIds = onlineUserIds,
|
||||
mongoCache = mongoCache,
|
||||
asyncCache = asyncCache,
|
||||
rankingApi = rankingApi
|
||||
|
@ -122,6 +115,7 @@ object Env {
|
|||
asyncCache = lila.memo.Env.current.asyncCache,
|
||||
scheduler = lila.common.PlayApp.scheduler,
|
||||
timeline = lila.hub.Env.current.timeline,
|
||||
onlineUserIds = lila.socket.Env.current.onlineUserIds,
|
||||
system = lila.common.PlayApp.system
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue