Skip to content

Commit

Permalink
integrate world map
Browse files Browse the repository at this point in the history
  • Loading branch information
ornicar committed Aug 25, 2014
1 parent ab9831d commit 7794a30
Show file tree
Hide file tree
Showing 25 changed files with 769 additions and 54 deletions.
1 change: 1 addition & 0 deletions app/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,5 @@ object Env {
def donation = lila.donation.Env.current
def qa = lila.qa.Env.current
def history = lila.history.Env.current
def worldMap = lila.worldMap.Env.current
}
10 changes: 2 additions & 8 deletions app/controllers/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,6 @@ object Main extends LilaController {
}
}

def stream = Action.async {
import lila.round.MoveBroadcast
Env.round.moveBroadcast ? MoveBroadcast.GetEnumerator mapTo
manifest[Enumerator[String]] map { e => Ok.feed(e) }
}

def captchaCheck(id: String) = Open { implicit ctx =>
Env.hub.actor.captcher ? ValidCaptcha(id, ~get("solution")) map {
case valid: Boolean => Ok(valid fold (1, 0))
Expand All @@ -61,13 +55,13 @@ object Main extends LilaController {

def developers = Open { implicit ctx =>
fuccess {
views.html.site.developers()
html.site.developers()
}
}

def irc = Open { implicit ctx =>
ctx.me ?? Env.team.api.mine map {
views.html.site.irc(_)
html.site.irc(_)
}
}
}
21 changes: 21 additions & 0 deletions app/controllers/WorldMap.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package controllers

import play.api.libs.EventSource
import play.api.libs.json._
import play.api.mvc._, Results._

import lila.app._
import views._

object WorldMap extends LilaController {

def index = Action {
Ok(views.html.site.worldMap())
}

def stream = Action {
Ok.chunked(
Env.worldMap.stream.producer &> EventSource()
) as "text/event-stream"
}
}
31 changes: 31 additions & 0 deletions app/views/site/worldMap.scala.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
@()

<!DOCTYPE html>

<html>
<head>
<title>Lichess World Map</title>
<link rel="shortcut icon" href="@routes.Assets.at("images/favicon-32-white.png")" type="image/x-icon" />
@cssAt("worldMap/main.css")
</head>
<body>
<h1><a href="http://lichess.org">lichess<span class="extension">.org</span></a> activity</h1>
<div id="worldmap"></div>
<div id="stats">
<div class="wrapL">
<div id="time">time: <span></span></div>
<div id="moves">moves: <span></span></div>
<div id="countries">countries: <span></span></div>
</div>
<div class="wrapL">
<div id="topCountries"></div>
</div>
</div>
@jQueryTag
@jsAt("worldMap/raphael-min.js")
@jsAt("worldMap/world.js")
@jsAt("worldMap/time.js")
@jsAt("worldMap/app.js")
@jsAt("worldMap/stats.js")
</body>
</html>
7 changes: 5 additions & 2 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -284,11 +284,14 @@ POST /api/puzzle controllers.Puzzle.importBatch

# Misc
POST /cli controllers.Cli.command
GET /captcha/$id<\w{8}> controllers.Main.captchaCheck(id: String)
GET /captcha/$id<\w{8}> controllers.Main.captchaCheck(id: String)
GET /developers controllers.Main.developers
GET /embed controllers.Main.embed
GET /irc controllers.Main.irc
GET /stream controllers.Main.stream

# Map
GET /world-map controllers.WorldMap.index
GET /world-map/stream controllers.WorldMap.stream

# Pages
GET /thanks controllers.Page.thanks
Expand Down
6 changes: 6 additions & 0 deletions modules/memo/src/main/Builder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ object Builder {
def expiry[K, V](ttl: Duration): Cache[K, V] =
cacheBuilder[K, V](ttl).build[K, V]

def size[K, V](max: Int): Cache[K, V] =
CacheBuilder.newBuilder()
.maximumSize(max)
.asInstanceOf[CacheBuilder[K, V]]
.build[K, V]

private def cacheBuilder[K, V](ttl: Duration): CacheBuilder[K, V] =
CacheBuilder.newBuilder()
.expireAfterWrite(ttl, TimeUnit.MILLISECONDS)
Expand Down
2 changes: 0 additions & 2 deletions modules/round/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ final class Env(
val CollectionReminder = config getString "collection.reminder"
val CasualOnly = config getBoolean "casual_only"
val ActiveTtl = config duration "active.ttl"
val StreamApiSalt = config getString "stream_api.salt"
}
import settings._

Expand Down Expand Up @@ -179,7 +178,6 @@ final class Env(
uciMemo = uciMemo,
prefApi = prefApi)

lazy val moveBroadcast = system.actorOf(Props(classOf[MoveBroadcast], StreamApiSalt))
lazy val tvBroadcast = system.actorOf(Props(classOf[TvBroadcast]))
}

Expand Down
40 changes: 20 additions & 20 deletions modules/round/src/main/MoveBroadcast.scala
Original file line number Diff line number Diff line change
@@ -1,33 +1,33 @@
package lila.round
// package lila.round

import akka.actor._
// import akka.actor._

import com.roundeights.hasher.Implicits._
// import com.roundeights.hasher.Implicits._

import lila.hub.actorApi.round.MoveEvent
import play.api.libs.iteratee._
// import lila.hub.actorApi.round.MoveEvent
// import play.api.libs.iteratee._

private final class MoveBroadcast(salt: String) extends Actor {
// private final class MoveBroadcast(salt: String) extends Actor {

context.system.lilaBus.subscribe(self, 'moveEvent)
// context.system.lilaBus.subscribe(self, 'moveEvent)

override def postStop() {
context.system.lilaBus.unsubscribe(self)
}
// override def postStop() {
// context.system.lilaBus.unsubscribe(self)
// }

private val (enumerator, channel) = Concurrent.broadcast[String]
// private val (enumerator, channel) = Concurrent.broadcast[String]

def receive = {
// def receive = {

case MoveBroadcast.GetEnumerator => sender ! enumerator
// case MoveBroadcast.GetEnumerator => sender ! enumerator

case move: MoveEvent => channel push s"${tokenOf(move.gameId)} ${move.ip}"
}
// case move: MoveEvent => channel push s"${tokenOf(move.gameId)} ${move.ip}"
// }

def tokenOf(gameId: String) = gameId.salt(salt).md5.hex take 8
}
// def tokenOf(gameId: String) = gameId.salt(salt).md5.hex take 8
// }

object MoveBroadcast {
// object MoveBroadcast {

case object GetEnumerator
}
// case object GetEnumerator
// }
13 changes: 5 additions & 8 deletions modules/round/src/main/VersionedEvent.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,11 @@ case class VersionedEvent(
watcher: Boolean,
troll: Boolean) {

def jsFor(m: Member): JsObject = visibleBy(m).fold(
Json.obj(
"v" -> version,
"t" -> typ,
"d" -> data
),
Json.obj("v" -> version)
)
def jsFor(m: Member): JsObject = if (visibleBy(m)) {
if (data == JsNull) Json.obj("v" -> version, "t" -> typ)
else Json.obj("v" -> version, "t" -> typ, "d" -> data)
}
else Json.obj("v" -> version)

private def visibleBy(m: Member): Boolean =
if (watcher && m.owner) false
Expand Down
2 changes: 1 addition & 1 deletion modules/security/src/main/Env.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ final class Env(

lazy val forms = new DataForm(captcher = captcher)

private lazy val geoIP = new GeoIP(
lazy val geoIP = new GeoIP(
file = GeoIPFile,
cacheSize = GeoIPCacheSize)

Expand Down
14 changes: 3 additions & 11 deletions modules/security/src/main/GeoIP.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,11 @@ package lila.security

import com.sanoma.cda.geoip.{ MaxMindIpGeo, IpLocation }

import lila.memo.AsyncCache
import scala.concurrent.Future
final class GeoIP(file: String, cacheSize: Int) {

private[security] final class GeoIP(file: String, cacheSize: Int) {
private val geoIp = MaxMindIpGeo(file, cacheSize)

private val geoIp = MaxMindIpGeo(file, 0)

private val cache = AsyncCache(
f = (ip: String) => Future { geoIp getLocation ip },
maxCapacity = cacheSize)

def apply(ip: String): Future[Location] =
cache(ip) map (_.fold(Location.unknown)(Location.apply))
def apply(ip: String): Option[Location] = geoIp getLocation ip map Location.apply
}

case class Location(
Expand Down
2 changes: 1 addition & 1 deletion modules/security/src/main/UserSpy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object UserSpy {
objs $find(Json.obj("user" -> user.id))
ips = objs.flatMap(_ str "ip").distinct
blockedIps (ips map firewall.blocksIp).sequenceFu
locations <- (ips map geoIP.apply).sequenceFu
locations <- scala.concurrent.Future { ips flatMap geoIP.apply }
users explore(Set(user), Set.empty, Set(user))
} yield UserSpy(
ips = ips zip blockedIps zip locations map {
Expand Down
30 changes: 30 additions & 0 deletions modules/worldMap/src/main/Env.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lila.worldMap

import com.typesafe.config.Config

import com.sanoma.cda.geoip.MaxMindIpGeo
import lila.common.PimpedConfig._

final class Env(
system: akka.actor.ActorSystem,
config: Config) {

private val GeoIPFile = config getString "geoip.file"
private val GeoIPCacheSize = config getInt "geoip.cache_size"
private val PlayersCacheSize = config getInt "players.cache_size"

lazy val players = new Players(PlayersCacheSize)

lazy val stream = new Stream(
system = system,
players = players,
geoIp = MaxMindIpGeo(GeoIPFile, GeoIPCacheSize))
}

object Env {

lazy val current: Env = "[boot] worldMap" describes new Env(
system = lila.common.PlayApp.system,
config = lila.common.PlayApp loadConfig "worldMap")
}

34 changes: 34 additions & 0 deletions modules/worldMap/src/main/Players.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package lila.worldMap

import lila.memo.Builder

private[worldMap] final class Players(cacheSize: Int) {

// to each game ID, associate list of player Locations
// there can be 0, 1 or 2 players per game ID,
// but this constraint is not expressed by the cache type :(
private val cache = Builder.size[String, List[Location]](cacheSize)

def getOpponentLocation(gameId: String, myLocation: Location): Option[Location] =

Option(cache getIfPresent gameId) getOrElse Nil match {

// new game ID, store player location
case Nil =>
cache.put(gameId, List(myLocation)); None

// only my location is known
case List(loc) if loc == myLocation => None

// only opponent location is known. Store mine
case List(loc) =>
cache.put(gameId, List(loc, myLocation)); Some(loc)

// both locations are known
case List(l1, l2) if l1 == myLocation => Some(l2)

// both locations are known
case List(l1, l2) if l2 == myLocation => Some(l1)
}
}

45 changes: 45 additions & 0 deletions modules/worldMap/src/main/Stream.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package lila.worldMap

import akka.actor._
import com.google.common.cache.LoadingCache
import com.sanoma.cda.geoip.{ MaxMindIpGeo, IpLocation }
import java.io.File
import lila.hub.actorApi.round.MoveEvent
import play.api.libs.iteratee._
import play.api.libs.json._

final class Stream(
system: ActorSystem,
players: Players,
geoIp: MaxMindIpGeo) {

private val (enumerator, channel) = Concurrent.broadcast[MoveEvent]

private val processor: Enumeratee[MoveEvent, String] =
Enumeratee.mapInput[MoveEvent].apply[String] {
case Input.El(move) =>
geoIp getLocation move.ip flatMap Location.apply match {
case None => Input.Empty
case Some(loc) =>
val opponentLoc = players.getOpponentLocation(move.gameId, loc)
Input.El(Json.stringify {
Json.obj(
"country" -> loc.country,
"lat" -> loc.lat,
"lon" -> loc.lon,
"oLat" -> opponentLoc.map(_.lat),
"oLon" -> opponentLoc.map(_.lon)
)
})
}
case _ => Input.Empty
}

val producer = enumerator &> processor

system.lilaBus.subscribe(system.actorOf(Props(new Actor {
def receive = {
case move: MoveEvent => channel push move
}
})), 'moveEvent)
}
16 changes: 16 additions & 0 deletions modules/worldMap/src/main/model.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package lila.worldMap

import com.sanoma.cda.geoip.IpLocation

case class Location(
country: String,
lat: Double,
lon: Double)

object Location {

def apply(ipLoc: IpLocation): Option[Location] = for {
country <- ipLoc.countryName
point <- ipLoc.geoPoint
} yield Location(country, point.latitude, point.longitude)
}
3 changes: 3 additions & 0 deletions modules/worldMap/src/main/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package lila

package object worldMap extends PackageObject with WithPlay
Loading

0 comments on commit 7794a30

Please sign in to comment.