summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorLi Haoyi <haoyi@dropbox.com>2014-09-03 23:06:31 -0700
committerLi Haoyi <haoyi@dropbox.com>2014-09-03 23:06:31 -0700
commit2f96da023d9172deb646de9bd1cf0a05c4201df7 (patch)
tree8636cf8853fe0a8621587e4a9870230545f89f9c /src/main
parent424cc28d19f7e23cab6af9795a6c6a5c4adf5e3c (diff)
downloadworkbench-2f96da023d9172deb646de9bd1cf0a05c4201df7.tar.gz
workbench-2f96da023d9172deb646de9bd1cf0a05c4201df7.tar.bz2
workbench-2f96da023d9172deb646de9bd1cf0a05c4201df7.zip
lols
Diffstat (limited to 'src/main')
-rw-r--r--src/main/scala/workbench/Plugin.scala74
-rw-r--r--src/main/scala/workbench/Server.scala83
2 files changed, 157 insertions, 0 deletions
diff --git a/src/main/scala/workbench/Plugin.scala b/src/main/scala/workbench/Plugin.scala
new file mode 100644
index 0000000..8e122fe
--- /dev/null
+++ b/src/main/scala/workbench/Plugin.scala
@@ -0,0 +1,74 @@
+package com.lihaoyi.workbench
+import sbt._
+import sbt.Keys._
+import autowire._
+import upickle.Js
+import scala.concurrent.ExecutionContext.Implicits.global
+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.Wire[Api].print(level.toString, message).call()
+ def success(message: => String) = server.value.Wire[Api].print("info", message).call()
+ def trace(t: => Throwable) = server.value.Wire[Api].print("error", t.toString).call()
+ }
+ }
+ clientLogger.setSuccessEnabled(true)
+ val currentFunction = extraLoggers.value
+ (key: ScopedKey[_]) => clientLogger +: currentFunction(key)
+ },
+ refreshBrowsers := {
+ streams.value.log.info("workbench: Reloading Pages...")
+ server.value.Wire[Api].reload().call()
+ },
+ 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.Wire[Api].clear().call()
+
+ changed.foreach { path =>
+ streams.value.log.info("workbench: Refreshing " + path)
+ server.value.Wire[Api].run(path, Some(bootSnippet.value)).call()
+ }
+ }
+ },
+ 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/src/main/scala/workbench/Server.scala b/src/main/scala/workbench/Server.scala
new file mode 100644
index 0000000..96ac736
--- /dev/null
+++ b/src/main/scala/workbench/Server.scala
@@ -0,0 +1,83 @@
+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 upickle.{Reader, Writer, Js}
+import spray.http.{AllOrigins, HttpResponse}
+import spray.http.HttpHeaders.`Access-Control-Allow-Origin`
+import concurrent.duration._
+import scala.concurrent.Future
+
+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
+ )
+ object Wire extends autowire.Client[Js.Value, upickle.Reader, upickle.Writer]{
+ def doCall(req: Request): Future[Js.Value] = {
+ pubSub ! Js.Arr(Js.Str(req.path.mkString(".")), Js.Obj(req.args.toSeq:_*))
+ Future.successful(Js.Null)
+ }
+ def write[Result: Writer](r: Result) = upickle.writeJs(r)
+ def read[Result: Reader](p: Js.Value) = upickle.readJs[Result](p)
+ }
+ private 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, upickle.json.write(Js.Arr(msgs:_*)))
+ queuedMessages = Nil
+ case (msg: Js.Arr, None, msgs) =>
+ queuedMessages = msg :: msgs
+ case (msg: Js.Arr, Some(a), Nil) =>
+ respond(a, upickle.json.write(Js.Arr(msg)))
+ waitingActor = None
+ case (Clear, Some(a), Nil) =>
+ respond(a, upickle.json.write(Js.Arr()))
+ waitingActor = None
+ }
+ })
+
+ startServer(url, port) {
+ get {
+ path("workbench.js") {
+ complete {
+ IO.readStream(
+ getClass.getClassLoader
+ .getResourceAsStream("client-opt.js")
+ ) + s"\nMain.main(${upickle.write(bootSnippet)}, ${upickle.write(url)}, ${upickle.write(port)})"
+ }
+ } ~
+ getFromDirectory(".")
+ } ~
+ post {
+ path("notifications") { ctx =>
+ pubSub ! ctx.responder
+ }
+ }
+ }
+ def kill() = {
+ system.shutdown()
+ }
+} \ No newline at end of file