mirror of https://github.com/sbt/sbt.git
Server hooked in as a CommandListener
This commit is contained in:
parent
f9dd8b73b7
commit
ff211d08f9
|
|
@ -210,6 +210,7 @@ object BasicCommands {
|
||||||
commandListers foreach { x =>
|
commandListers foreach { x =>
|
||||||
x.run(commandQueue, CommandStatus(s, true))
|
x.run(commandQueue, CommandStatus(s, true))
|
||||||
}
|
}
|
||||||
|
askingAlready = true
|
||||||
}
|
}
|
||||||
blockUntilNextCommand match {
|
blockUntilNextCommand match {
|
||||||
case Some(line) =>
|
case Some(line) =>
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,37 @@
|
||||||
package sbt
|
package sbt
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
import server.Server
|
import sbt.server._
|
||||||
|
|
||||||
|
private[sbt] final class NetworkListener extends CommandListener {
|
||||||
|
|
||||||
|
private var server: Option[ServerInstance] = None
|
||||||
|
|
||||||
class NetworkListener extends CommandListener {
|
|
||||||
def run(queue: ConcurrentLinkedQueue[Option[String]],
|
def run(queue: ConcurrentLinkedQueue[Option[String]],
|
||||||
status: CommandStatus): Unit =
|
status: CommandStatus): Unit =
|
||||||
{
|
{
|
||||||
val server = Server.start("127.0.0.1", 12700)
|
def onCommand(command: sbt.server.Command): Unit = {
|
||||||
// spawn thread and loop
|
command match {
|
||||||
|
case Execution(cmd) => queue.add(Some(cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server = Some(Server.start("127.0.0.1", 12700, onCommand))
|
||||||
}
|
}
|
||||||
|
|
||||||
def shutdown(): Unit =
|
def shutdown(): Unit =
|
||||||
{
|
{
|
||||||
// interrupt and kill the thread
|
// interrupt and kill the thread
|
||||||
|
server.foreach(_.shutdown())
|
||||||
}
|
}
|
||||||
|
|
||||||
def setStatus(status: CommandStatus): Unit = ???
|
def setStatus(cmdStatus: CommandStatus): Unit = {
|
||||||
|
server.foreach(server =>
|
||||||
|
server.publish(
|
||||||
|
if (cmdStatus.canEnter) StatusEvent(Ready)
|
||||||
|
else StatusEvent(Processing("TODO current command", cmdStatus.state.remainingCommands))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
abstract class ClientConnection(connection: Socket) {
|
abstract class ClientConnection(connection: Socket) {
|
||||||
|
|
||||||
// TODO handle client disconnect
|
|
||||||
private val running = new AtomicBoolean(true)
|
private val running = new AtomicBoolean(true)
|
||||||
private val delimiter: Byte = '\n'.toByte
|
private val delimiter: Byte = '\n'.toByte
|
||||||
|
|
||||||
|
|
@ -17,6 +16,7 @@ abstract class ClientConnection(connection: Socket) {
|
||||||
|
|
||||||
val thread = new Thread(s"sbt-client-${connection.getPort}") {
|
val thread = new Thread(s"sbt-client-${connection.getPort}") {
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
|
try {
|
||||||
val readBuffer = new Array[Byte](4096)
|
val readBuffer = new Array[Byte](4096)
|
||||||
val in = connection.getInputStream
|
val in = connection.getInputStream
|
||||||
connection.setSoTimeout(5000)
|
connection.setSoTimeout(5000)
|
||||||
|
|
@ -32,7 +32,7 @@ abstract class ClientConnection(connection: Socket) {
|
||||||
val delimPos = bytes.indexOf(delimiter)
|
val delimPos = bytes.indexOf(delimiter)
|
||||||
if (delimPos > 0) {
|
if (delimPos > 0) {
|
||||||
val chunk = buffer.take(delimPos)
|
val chunk = buffer.take(delimPos)
|
||||||
buffer = buffer.drop(delimPos)
|
buffer = buffer.drop(delimPos + 1)
|
||||||
|
|
||||||
Serialization.deserialize(chunk).fold(
|
Serialization.deserialize(chunk).fold(
|
||||||
errorDesc => println("Got invalid chunk from client: " + errorDesc),
|
errorDesc => println("Got invalid chunk from client: " + errorDesc),
|
||||||
|
|
@ -45,9 +45,11 @@ abstract class ClientConnection(connection: Socket) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
shutdown()
|
shutdown()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|
||||||
def publish(event: Array[Byte]): Unit = {
|
def publish(event: Array[Byte]): Unit = {
|
||||||
|
|
|
||||||
|
|
@ -17,25 +17,32 @@ object Serialization {
|
||||||
}
|
}
|
||||||
|
|
||||||
def toJson(event: Event): JObject = event match {
|
def toJson(event: Event): JObject = event match {
|
||||||
case LogEvent() =>
|
case LogEvent(level, message) =>
|
||||||
JObject(
|
JObject(
|
||||||
"type" -> JString("log_event"),
|
"type" -> JString("log_event"),
|
||||||
"level" -> JString("INFO"),
|
"level" -> JString(level),
|
||||||
"message" -> JString("todo")
|
"message" -> JString(message)
|
||||||
)
|
)
|
||||||
|
|
||||||
case StatusEvent() =>
|
case StatusEvent(Ready) =>
|
||||||
JObject(
|
JObject(
|
||||||
"type" -> JString("status_event"),
|
"type" -> JString("status_event"),
|
||||||
"status" -> JString("ready"),
|
"status" -> JString("ready"),
|
||||||
"command_queue" -> JArray(List.empty)
|
"command_queue" -> JArray(List.empty)
|
||||||
)
|
)
|
||||||
|
|
||||||
case ExecutionEvent() =>
|
case StatusEvent(Processing(command, commandQueue)) =>
|
||||||
|
JObject(
|
||||||
|
"type" -> JString("status_event"),
|
||||||
|
"status" -> JString("processing"),
|
||||||
|
"command_queue" -> JArray(commandQueue.map(JString).toList)
|
||||||
|
)
|
||||||
|
|
||||||
|
case ExecutionEvent(command, status) =>
|
||||||
JObject(
|
JObject(
|
||||||
"type" -> JString("execution_event"),
|
"type" -> JString("execution_event"),
|
||||||
"command" -> JString("project todo"),
|
"command" -> JString(command),
|
||||||
"success" -> JArray(List.empty)
|
"success" -> JBool(status)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,13 +54,16 @@ object Serialization {
|
||||||
val json = parse(new String(bytes.toArray, "UTF-8"))
|
val json = parse(new String(bytes.toArray, "UTF-8"))
|
||||||
implicit val formats = DefaultFormats
|
implicit val formats = DefaultFormats
|
||||||
|
|
||||||
// TODO: is using extract safe?
|
(json \ "type").toOption match {
|
||||||
(json \ "type").extract[String] match {
|
case Some(JString("execution")) =>
|
||||||
case "execution" => Right(Execution((json \ "command_line").extract[String]))
|
(json \ "command_line").toOption match {
|
||||||
case cmd => Left(s"Unknown command type $cmd")
|
case Some(JString(cmd)) => Right(Execution(cmd))
|
||||||
|
case _ => Left("Missing or invalid command_line field")
|
||||||
|
}
|
||||||
|
case Some(cmd) => Left(s"Unknown command type $cmd")
|
||||||
|
case None => Left("Invalid command, missing type field")
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
case e: ParseException => Left(s"Parse error: ${e.getMessage}")
|
case e: ParseException => Left(s"Parse error: ${e.getMessage}")
|
||||||
case e: MappingException => Left(s"Missing type field")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,27 +8,25 @@ import java.net.{ SocketTimeoutException, InetAddress, ServerSocket }
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
import java.util.concurrent.ConcurrentLinkedQueue
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
sealed trait ServerInstance {
|
private[sbt] sealed trait ServerInstance {
|
||||||
def shutdown(): Unit
|
def shutdown(): Unit
|
||||||
def nextCommand(): Option[Command]
|
def publish(event: Event): Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
object Server {
|
private[sbt] object Server {
|
||||||
def start(host: String, port: Int): ServerInstance =
|
def start(host: String, port: Int, onIncommingCommand: Command => Unit): ServerInstance =
|
||||||
new ServerInstance {
|
new ServerInstance {
|
||||||
|
|
||||||
val commandQueue = new ConcurrentLinkedQueue[Command]()
|
|
||||||
|
|
||||||
val lock = new AnyRef {}
|
val lock = new AnyRef {}
|
||||||
var clients = Vector[ClientConnection]()
|
var clients = Vector[ClientConnection]()
|
||||||
val running = new AtomicBoolean(true)
|
val running = new AtomicBoolean(true)
|
||||||
|
|
||||||
val serverSocket = new ServerSocket(port, 50, InetAddress.getByName(host))
|
|
||||||
serverSocket.setSoTimeout(5000)
|
|
||||||
|
|
||||||
val serverThread = new Thread("sbt-socket-server") {
|
val serverThread = new Thread("sbt-socket-server") {
|
||||||
|
|
||||||
override def run(): Unit = {
|
override def run(): Unit = {
|
||||||
|
val serverSocket = new ServerSocket(port, 50, InetAddress.getByName(host))
|
||||||
|
serverSocket.setSoTimeout(5000)
|
||||||
|
|
||||||
println(s"SBT socket server started at $host:$port")
|
println(s"SBT socket server started at $host:$port")
|
||||||
while (running.get()) {
|
while (running.get()) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -37,8 +35,7 @@ object Server {
|
||||||
|
|
||||||
val connection = new ClientConnection(socket) {
|
val connection = new ClientConnection(socket) {
|
||||||
override def onCommand(command: Command): Unit = {
|
override def onCommand(command: Command): Unit = {
|
||||||
println(s"onCommand $command")
|
onIncommingCommand(command)
|
||||||
commandQueue.add(command)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,13 +61,6 @@ object Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The next queued command if there is one. It will have to be consumed because it is taken off the queue.
|
|
||||||
*/
|
|
||||||
def nextCommand(): Option[Command] = {
|
|
||||||
Option(commandQueue.poll())
|
|
||||||
}
|
|
||||||
|
|
||||||
override def shutdown(): Unit = {
|
override def shutdown(): Unit = {
|
||||||
println("Shutting down server")
|
println("Shutting down server")
|
||||||
running.set(false)
|
running.set(false)
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,22 @@
|
||||||
package sbt
|
package sbt
|
||||||
package server
|
package server
|
||||||
|
|
||||||
trait Event
|
/*
|
||||||
|
* These classes are the protocol for client-server interaction,
|
||||||
|
* commands can come from the client side, while events are emitted
|
||||||
|
* from sbt to inform the client of state changes etc.
|
||||||
|
*/
|
||||||
|
private[sbt] sealed trait Event
|
||||||
|
|
||||||
case class LogEvent() extends Event
|
private[sbt] final case class LogEvent(level: String, message: String) extends Event
|
||||||
case class StatusEvent() extends Event
|
|
||||||
case class ExecutionEvent() extends Event
|
|
||||||
|
|
||||||
trait Command
|
sealed trait Status
|
||||||
|
private[sbt] final case object Ready extends Status
|
||||||
|
private[sbt] final case class Processing(command: String, commandQueue: Seq[String]) extends Status
|
||||||
|
|
||||||
case class Execution(cmd: String) extends Command
|
private[sbt] final case class StatusEvent(status: Status) extends Event
|
||||||
|
private[sbt] final case class ExecutionEvent(command: String, success: Boolean) extends Event
|
||||||
|
|
||||||
|
private[sbt] sealed trait Command
|
||||||
|
|
||||||
|
private[sbt] final case class Execution(cmd: String) extends Command
|
||||||
Loading…
Reference in New Issue