1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
|
package com.lihaoyi.workbench
import akka.actor.{ActorRef, Actor, ActorSystem}
import scala.concurrent.duration._
import sbt._
import Keys._
import akka.actor.ActorDSL._
import com.typesafe.config.ConfigFactory
import upickle._
import spray.http.{AllOrigins, HttpResponse}
import spray.routing.SimpleRoutingApp
import spray.http.HttpHeaders.`Access-Control-Allow-Origin`
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")
val updatedJS = taskKey[List[String]]("Provides the addresses of the JS files that have changed")
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
}
})
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 := {
val clientLogger = FullLogger{
new Logger {
def log(level: Level.Value, message: => String) =
if(level >= Level.Info) pubSub ! upickle.writeJs(Seq("print", level.toString(), message))
def success(message: => String) = pubSub ! upickle.writeJs(Seq("print", "info", message))
def trace(t: => Throwable) = pubSub ! upickle.writeJs(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...")
pubSub ! upickle.writeJs(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) {
pubSub ! upickle.writeJs(Seq("clear"))
changed.foreach {
path =>
streams.value.log.info("workbench: Refreshing " + path)
pubSub ! upickle.writeJs(Seq(
"run",
path,
bootSnippet.value
))
}
}
},
routes := startServer(localUrl.value._1, localUrl.value._2){
get{
path("workbench.js"){
complete{
IO.readStream(
getClass.getClassLoader
.getResourceAsStream("workbench_template.js")
).replace("<host>", localUrl.value._1)
.replace("<port>", localUrl.value._2.toString)
.replace("<bootSnippet>", bootSnippet.value)
}
} ~
getFromDirectory(".")
} ~
post{
path("notifications"){ ctx =>
pubSub ! ctx.responder
}
}
}
)
}
|