full remote sockets WIP

full-remote-socket
Thibault Duplessis 2019-11-25 15:36:39 -06:00
parent bba93795fe
commit ec0aad51ae
71 changed files with 105 additions and 1502 deletions

View File

@ -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, _)

View File

@ -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))

View File

@ -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)))
}

View File

@ -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)

View File

@ -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,

View File

@ -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]

View File

@ -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),

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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])

View File

@ -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)
}

View File

@ -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")

View File

@ -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 =>

View File

@ -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 {

View File

@ -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,

View File

@ -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 {

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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")
}

View File

@ -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)

View File

@ -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
)
}

View File

@ -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
)
}

View File

@ -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")

View File

@ -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 = {

View File

@ -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]]
)
}

View File

@ -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

View File

@ -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 {

View File

@ -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)
)
}
}

View File

@ -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])

View File

@ -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]

View File

@ -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")
}

View File

@ -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,

View File

@ -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

View File

@ -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
) {

View File

@ -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

View File

@ -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 {

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
)
}

View File

@ -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)
}
}

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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)
}
}
}
}
}

View File

@ -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 {

View File

@ -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)
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -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")
}

View File

@ -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]

View File

@ -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 }
}

View File

@ -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)
}

View File

@ -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])

View File

@ -1,6 +1,3 @@
package lila
package object socket extends PackageObject with socket.WithSocket {
val logger = lila.log("socket")
}
package object socket extends PackageObject

View File

@ -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}",

View File

@ -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
)

View File

@ -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)
}

View File

@ -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 }

View File

@ -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)

View File

@ -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")

View File

@ -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,

View File

@ -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

View File

@ -14,7 +14,7 @@ import lila.user.User
private final class TournamentSocket(
remoteSocketApi: lila.socket.RemoteSocket,
chat: ActorSelection,
chat: lila.chat.ChatApi,
system: ActorSystem
) {

View File

@ -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,

View File

@ -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 {

View File

@ -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

View File

@ -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
)
}