diff options
author | Li Haoyi <haoyi@dropbox.com> | 2014-08-18 00:59:08 -0700 |
---|---|---|
committer | Li Haoyi <haoyi@dropbox.com> | 2014-08-18 00:59:08 -0700 |
commit | 424cc28d19f7e23cab6af9795a6c6a5c4adf5e3c (patch) | |
tree | ceaef4aab4e9377330a452da0742981e12611e97 /plugin | |
parent | 6dcc72f2fa678256c3529e51c83d2d77f711bdf2 (diff) | |
download | workbench-scalajs.tar.gz workbench-scalajs.tar.bz2 workbench-scalajs.zip |
wipscalajs
Diffstat (limited to 'plugin')
-rw-r--r-- | plugin/src/main/scala/workbench/Plugin.scala | 75 | ||||
-rw-r--r-- | plugin/src/main/scala/workbench/Server.scala | 80 |
2 files changed, 155 insertions, 0 deletions
diff --git a/plugin/src/main/scala/workbench/Plugin.scala b/plugin/src/main/scala/workbench/Plugin.scala new file mode 100644 index 0000000..3a434e8 --- /dev/null +++ b/plugin/src/main/scala/workbench/Plugin.scala @@ -0,0 +1,75 @@ +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 localUrl = settingKey[(String, Int)]("localUrl") + private[this] val server = settingKey[Server]("local websocket server") + + + val bootSnippet = settingKey[String]("piece of javascript to make things happen") + val updatedJS = taskKey[List[String]]("Provides the addresses of the JS files that have changed") + + + + val workbenchSettings = Seq( + localUrl := ("localhost", 12345), + updatedJS := { + var files: List[String] = Nil + ((crossTarget in Compile).value * "*.js").get.foreach { + (x: File) => + streams.value.log.info("workbench: Checking " + x.getName) + FileFunction.cached(streams.value.cacheDirectory / x.getName, FilesInfo.lastModified, FilesInfo.lastModified) { + (f: Set[File]) => + val fsPath = f.head.getAbsolutePath.drop(new File("").getAbsolutePath.length) + files = fsPath :: files + f + }(Set(x)) + } + files + }, + updatedJS <<= (updatedJS, localUrl) map { (paths, localUrl) => + paths.map { path => + s"http://${localUrl._1}:${localUrl._2}$path" + } + }, + (extraLoggers in ThisBuild) := { + val clientLogger = FullLogger{ + new Logger { + def log(level: Level.Value, message: => String) = + if(level >= Level.Info) server.value msg Seq("print", level.toString, message) + def success(message: => String) = server.value msg Seq("print", "info", message) + def trace(t: => Throwable) = server.value msg Seq("print", "error", t.toString) + } + } + clientLogger.setSuccessEnabled(true) + val currentFunction = extraLoggers.value + (key: ScopedKey[_]) => clientLogger +: currentFunction(key) + }, + refreshBrowsers := { + streams.value.log.info("workbench: Reloading Pages...") + server.value msg Seq("reload") + }, + updateBrowsers := { + val changed = updatedJS.value + // There is no point in clearing the browser if no js files have changed. + if (changed.length > 0) { + server.value msg Seq("clear") + + changed.foreach { + path => + streams.value.log.info("workbench: Refreshing " + path) + server.value msg Seq( + "run", + path, + bootSnippet.value + ) + } + } + }, + server := new Server(localUrl.value._1, localUrl.value._2, bootSnippet.value), + (onUnload in Global) := { (onUnload in Global).value.compose{ state => + server.value.kill() + state + }} + ) +} diff --git a/plugin/src/main/scala/workbench/Server.scala b/plugin/src/main/scala/workbench/Server.scala new file mode 100644 index 0000000..b9773a7 --- /dev/null +++ b/plugin/src/main/scala/workbench/Server.scala @@ -0,0 +1,80 @@ +package com.lihaoyi.workbench + +import akka.actor.{ActorRef, Actor, ActorSystem} +import com.typesafe.config.ConfigFactory +import sbt.IO +import spray.routing.SimpleRoutingApp +import akka.actor.ActorDSL._ +import scala.Some +import upickle.{Writer, Json, Js} +import spray.http.{AllOrigins, HttpResponse} +import spray.http.HttpHeaders.`Access-Control-Allow-Origin` +import concurrent.duration._ +class Server(url: String, port: Int, bootSnippet: String) extends SimpleRoutingApp{ + implicit val system = ActorSystem( + "SystemLol", + config = ConfigFactory.load(ActorSystem.getClass.getClassLoader), + classLoader = ActorSystem.getClass.getClassLoader + ) + + val pubSub = actor(new Actor{ + var waitingActor: Option[ActorRef] = None + var queuedMessages = List[Js.Value]() + 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, Json.write(Js.Array(msgs))) + queuedMessages = Nil + case (msg: Js.Array, None, msgs) => + queuedMessages = msg :: msgs + case (msg: Js.Array, Some(a), Nil) => + respond(a, Json.write(Js.Array(Seq(msg)))) + waitingActor = None + case (Clear, Some(a), Nil) => + respond(a, Json.write(Js.Array(Nil))) + waitingActor = None + } + }) + + startServer(url, port) { + get { + path("workbench.js") { + complete { + IO.readStream( + getClass.getClassLoader + .getResourceAsStream("workbench_template.js") + ).replace("<host>", url) + .replace("<port>", port.toString) + .replace("<bootSnippet>", bootSnippet) + } + } ~ + getFromDirectory(".") + } ~ + post { + path("notifications") { ctx => + pubSub ! ctx.responder + } + } + } + + def msg[T: Writer](t: T) = { + pubSub ! upickle.writeJs(t) + } + def kill() = { + system.shutdown() + } +}
\ No newline at end of file |