summaryrefslogtreecommitdiff
path: root/src/main/scala/workbench/Server.scala
blob: bec88b67eaec65a9c509790cbc96d7fe5d71fbad (plain) (blame)
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
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(
    "Workbench-System",
    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(upickle.writeJs(req.path), 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 {
          val body = IO.readStream(
            getClass.getClassLoader.getResourceAsStream("client-opt.js")
          )
          s"""
          (function(){
            $body

            WorkbenchClient().main(${upickle.write(bootSnippet)}, ${upickle.write(url)}, ${upickle.write(port)})
          }).call(this)
          """
        }
      } ~
      getFromDirectory(".")
    } ~
    post {
      path("notifications") { ctx =>
        pubSub ! ctx.responder
      }
    }
  }
  def kill() = {
    system.shutdown()
  }
}