summaryrefslogtreecommitdiff
path: root/Plugin.scala
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-03-02 23:35:28 -0800
committerLi Haoyi <haoyi@dropbox.com>2014-03-02 23:35:28 -0800
commit9a5ecb105f6015aa50eddadafd3254b8751dc645 (patch)
tree9a362c9157c5922357ca34affc82104d1ec471d4 /Plugin.scala
parent78b15120d3b146e7dc6d6822bcebc711d0c97a9d (diff)
downloadworkbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.tar.gz
workbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.tar.bz2
workbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.zip
version 0.1 published
Diffstat (limited to 'Plugin.scala')
-rw-r--r--Plugin.scala178
1 files changed, 68 insertions, 110 deletions
diff --git a/Plugin.scala b/Plugin.scala
index 1249d3a..0d18d2f 100644
--- a/Plugin.scala
+++ b/Plugin.scala
@@ -1,71 +1,72 @@
package com.lihaoyi.workbench
-import akka.actor.{Props, ActorRef, Actor, ActorSystem}
-import akka.io
-import akka.util.ByteString
-import play.api.libs.json.JsArray
-import java.nio.file.{Files, Paths}
+import akka.actor.{ActorRef, Actor, ActorSystem}
+import scala.concurrent.duration._
+import java.nio.file.{Paths}
import play.api.libs.json.Json
-import spray.can.Http
-import spray.can.server.websockets.model.Frame
-import spray.can.server.websockets.model.OpCode
-import spray.can.server.websockets.Sockets
import sbt._
import Keys._
-
+import akka.actor.ActorDSL._
import com.typesafe.config.ConfigFactory
-import scala.collection.mutable
-import akka.io.Tcp
-import spray.http._
-import spray.http.HttpHeaders.{`Access-Control-Allow-Origin`, Connection}
-import spray.can.server.websockets.model.OpCode.Text
-import spray.http.HttpRequest
import play.api.libs.json.JsArray
-import spray.http.HttpResponse
-import java.io.IOException
+import spray.http.{AllOrigins, HttpResponse}
+import spray.routing.SimpleRoutingApp
+import spray.http.HttpHeaders.`Access-Control-Allow-Origin`
-object Plugin extends sbt.Plugin {
-
- val refreshBrowsers = taskKey[Unit]("Sends a message to all connected web pages asking them to refresh the page")
- val updateBrowsers = taskKey[Unit]("partially resets some of the stuff in the browser")
- val generateClient = taskKey[File]("generates a .js file that can be embedded in your web page")
- val localUrl = settingKey[(String, Int)]("localUrl")
- private[this] val server = settingKey[ActorRef]("local websocket server")
- val fileName = settingKey[String]("name of the generated javascript file")
- val bootSnippet = settingKey[String]("piece of javascript to make things happen")
-
+object Plugin extends sbt.Plugin with SimpleRoutingApp{
implicit val system = ActorSystem(
"SystemLol",
config = ConfigFactory.load(ActorSystem.getClass.getClassLoader),
classLoader = ActorSystem.getClass.getClassLoader
)
+ val refreshBrowsers = taskKey[Unit]("Sends a message to all connected web pages asking them to refresh the page")
+ val updateBrowsers = taskKey[Unit]("partially resets some of the stuff in the browser")
+ val localUrl = settingKey[(String, Int)]("localUrl")
+ private[this] val routes = settingKey[Unit]("local websocket server")
+ val bootSnippet = settingKey[String]("piece of javascript to make things happen")
- implicit class pimpedActor(server: ActorRef){
- def send(x: JsArray) = {
- server ! Frame(
- opcode = OpCode.Text,
- data = ByteString(x.toString())
+ val pubSub = actor(new Actor{
+ var waitingActor: Option[ActorRef] = None
+ var queuedMessages = List[JsArray]()
+ case object Clear
+ import system.dispatcher
+
+ system.scheduler.schedule(0 seconds, 10 seconds, self, Clear)
+ def respond(a: ActorRef, s: String) = {
+ a ! HttpResponse(
+ entity = s,
+ headers = List(`Access-Control-Allow-Origin`(AllOrigins))
)
}
- }
+ def receive = (x: Any) => (x, waitingActor, queuedMessages) match {
+ case (a: ActorRef, _, Nil) =>
+ // Even if there's someone already waiting,
+ // a new actor waiting replaces the old one
+ waitingActor = Some(a)
+ case (a: ActorRef, None, msgs) =>
+ respond(a, "[" + msgs.mkString(",") + "]")
+ queuedMessages = Nil
+ case (msg: JsArray, None, msgs) =>
+ queuedMessages = msg :: msgs
+ case (msg: JsArray, Some(a), Nil) =>
+ respond(a, "[" + msg + "]")
+ waitingActor = None
+ case (Clear, Some(a), Nil) =>
+ respond(a, "[]")
+ waitingActor = None
+ }
+ })
- val buildSettingsX = Seq(
+ val workbenchSettings = Seq(
localUrl := ("localhost", 12345),
- fileName := "workbench.js",
- server := {
- implicit val server = system.actorOf(Props(new SocketServer()))
- val host = localUrl.value
- io.IO(Sockets) ! Http.Bind(server, host._1, host._2)
- server
- },
extraLoggers := {
val clientLogger = FullLogger{
new Logger {
- def log(level: Level.Value, message: => String): Unit =
- if(level >= Level.Info) server.value.send(Json.arr("print", level.toString(), message))
- def success(message: => String): Unit = server.value.send(Json.arr("print", "info", message))
- def trace(t: => Throwable): Unit = server.value.send(Json.arr("print", "error", t.toString))
+ def log(level: Level.Value, message: => String) =
+ if(level >= Level.Info) pubSub ! Json.arr("print", level.toString(), message)
+ def success(message: => String) = pubSub ! Json.arr("print", "info", message)
+ def trace(t: => Throwable) = pubSub ! Json.arr("print", "error", t.toString)
}
}
clientLogger.setSuccessEnabled(true)
@@ -74,18 +75,17 @@ object Plugin extends sbt.Plugin {
},
refreshBrowsers := {
streams.value.log.info("workbench: Reloading Pages...")
- server.value.send(Json.arr("reload"))
+ pubSub ! Json.arr("reload")
},
updateBrowsers := {
-
- server.value send Json.arr("clear")
+ pubSub ! Json.arr("clear")
((crossTarget in Compile).value * "*.js").get.map{ (x: File) =>
streams.value.log.info("workbench: Checking " + x.getName)
FileFunction.cached(streams.value.cacheDirectory / x.getName, FilesInfo.lastModified, FilesInfo.lastModified){ (f: Set[File]) =>
streams.value.log.info("workbench: Refreshing " + x.getName)
val cwd = Paths.get(new File("").getAbsolutePath)
val filePath = Paths.get(f.head.getAbsolutePath)
- server.value send Json.arr(
+ pubSub ! Json.arr(
"run",
"/" + cwd.relativize(filePath).toString,
bootSnippet.value
@@ -94,68 +94,26 @@ object Plugin extends sbt.Plugin {
}(Set(x))
}
},
- generateClient := {
- FileFunction.cached(streams.value.cacheDirectory / "workbench"/ "workbench.js", FilesInfo.full, FilesInfo.exists){ (f: Set[File]) =>
- val transformed =
- IO.read(f.head)
- .replace("<host>", localUrl.value._1)
- .replace("<port>", localUrl.value._2.toString)
- .replace("<bootSnippet>", bootSnippet.value)
- val outputFile = (crossTarget in Compile).value / fileName.value
- IO.write(outputFile, transformed)
- Set(outputFile)
- }(Set(new File(getClass.getClassLoader.getResource("workbench_template.ts").toURI))).head
- }
- )
-
- class SocketServer() extends Actor{
- val sockets: mutable.Set[ActorRef] = mutable.Set.empty
- def receive = {
- case x: Tcp.Connected => sender ! Tcp.Register(self) // normal Http server init
-
- case req: HttpRequest =>
- // Upgrade the connection to websockets if you think the incoming
- // request looks good
- if (req.headers.contains(Connection("Upgrade"))){
- sender ! Sockets.UpgradeServer(Sockets.acceptAllFunction(req), self)
- }else{
-
-
- try{
- val data = Files.readAllBytes(
- Paths.get(req.uri.path.toString.drop(1))
- )
- val mimeType: ContentType = req.uri.path.toString.split('.').lastOption match {
- case Some("css") => MediaTypes.`text/css`
- case Some("html") => MediaTypes.`text/html`
- case Some("js") => MediaTypes.`application/javascript`
- case _ => ContentTypes.`text/plain`
- }
- sender ! HttpResponse(
- StatusCodes.OK,
- entity=HttpEntity.apply(mimeType, data),
- headers=List(
- `Access-Control-Allow-Origin`(spray.http.AllOrigins)
- )
- )
- }catch{case _: IOException =>
- sender ! HttpResponse(StatusCodes.NotFound)
+ routes := startServer(localUrl.value._1, localUrl.value._2){
+ get{
+ path("workbench.js"){
+ complete{
+ IO.readStream(
+ getClass.getClassLoader
+ .getResourceAsStream("workbench_template.ts")
+ ).replace("<host>", localUrl.value._1)
+ .replace("<port>", localUrl.value._2.toString)
+ .replace("<bootSnippet>", bootSnippet.value)
}
+ } ~
+ getFromDirectory(".")
+ } ~
+ post{
+ path("notifications"){ ctx =>
+ pubSub ! ctx.responder
}
+ }
- case Sockets.Upgraded =>
- sockets.add(sender)
- println("Browser Open n=" + sockets.size)
- self send Json.arr("boot")
-
- case f @ Frame(fin, rsv, Text, maskingKey, data) =>
- sockets.foreach(_ ! f.copy(maskingKey=None))
-
- case _: Tcp.ConnectionClosed =>
- if (sockets.contains(sender)) println("Browser Closed n=" + sockets.size )
- sockets.remove(sender)
-
- case x =>
}
- }
+ )
}