diff options
author | Li Haoyi <haoyi@dropbox.com> | 2014-03-02 23:35:28 -0800 |
---|---|---|
committer | Li Haoyi <haoyi@dropbox.com> | 2014-03-02 23:35:28 -0800 |
commit | 9a5ecb105f6015aa50eddadafd3254b8751dc645 (patch) | |
tree | 9a362c9157c5922357ca34affc82104d1ec471d4 /Plugin.scala | |
parent | 78b15120d3b146e7dc6d6822bcebc711d0c97a9d (diff) | |
download | workbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.tar.gz workbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.tar.bz2 workbench-9a5ecb105f6015aa50eddadafd3254b8751dc645.zip |
version 0.1 published
Diffstat (limited to 'Plugin.scala')
-rw-r--r-- | Plugin.scala | 178 |
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 => } - } + ) } |