From 892e25d23f27e56d7b9a1e8609b890a63718f13c Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Tue, 6 Dec 2016 22:31:27 -0500 Subject: [PATCH] Introduce execId that gets sent back Now the client can put an id on each exec. This can then be tracked and/or be used to block the user input. --- .../src/main/contraband-scala/sbt/Exec.scala | 27 ++++++++++++------- main-command/src/main/contraband/state.contra | 3 +++ .../src/main/scala/sbt/BasicCommands.scala | 9 +++---- main-command/src/main/scala/sbt/Command.scala | 8 +++--- .../scala/sbt/internal/ConsoleChannel.scala | 4 +-- .../sbt/internal/client/NetworkClient.scala | 19 ++++++++++--- .../sbt/internal/server/NetworkChannel.scala | 2 +- .../sbt/protocol/ExecStatusEvent.scala | 27 ++++++++++++++----- .../codec/ExecStatusEventFormats.scala | 6 ++++- protocol/src/main/contraband/server.contra | 2 ++ 10 files changed, 76 insertions(+), 31 deletions(-) diff --git a/main-command/src/main/contraband-scala/sbt/Exec.scala b/main-command/src/main/contraband-scala/sbt/Exec.scala index e618f49a7..ac3323c35 100644 --- a/main-command/src/main/contraband-scala/sbt/Exec.scala +++ b/main-command/src/main/contraband-scala/sbt/Exec.scala @@ -6,26 +6,33 @@ package sbt final class Exec private ( val commandLine: String, + val execId: Option[String], val source: Option[sbt.CommandSource]) extends Serializable { - + private def this(commandLine: String, source: Option[sbt.CommandSource]) = this(commandLine, None, source) override def equals(o: Any): Boolean = o match { - case x: Exec => (this.commandLine == x.commandLine) && (this.source == x.source) + case x: Exec => (this.commandLine == x.commandLine) && (this.execId == x.execId) && (this.source == x.source) case _ => false } override def hashCode: Int = { - 37 * (37 * (17 + commandLine.##) + source.##) + 37 * (37 * (37 * (17 + commandLine.##) + execId.##) + source.##) } override def toString: String = { - "Exec(" + commandLine + ", " + source + ")" + "Exec(" + commandLine + ", " + execId + ", " + source + ")" } - protected[this] def copy(commandLine: String = commandLine, source: Option[sbt.CommandSource] = source): Exec = { - new Exec(commandLine, source) + protected[this] def copy(commandLine: String = commandLine, execId: Option[String] = execId, source: Option[sbt.CommandSource] = source): Exec = { + new Exec(commandLine, execId, source) } def withCommandLine(commandLine: String): Exec = { copy(commandLine = commandLine) } + def withExecId(execId: Option[String]): Exec = { + copy(execId = execId) + } + def withExecId(execId: String): Exec = { + copy(execId = Option(execId)) + } def withSource(source: Option[sbt.CommandSource]): Exec = { copy(source = source) } @@ -34,7 +41,9 @@ final class Exec private ( } } object Exec { - - def apply(commandLine: String, source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, source) - def apply(commandLine: String, source: sbt.CommandSource): Exec = new Exec(commandLine, Option(source)) + def newExecId: String = java.util.UUID.randomUUID.toString + def apply(commandLine: String, source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, None, source) + def apply(commandLine: String, source: sbt.CommandSource): Exec = new Exec(commandLine, None, Option(source)) + def apply(commandLine: String, execId: Option[String], source: Option[sbt.CommandSource]): Exec = new Exec(commandLine, execId, source) + def apply(commandLine: String, execId: String, source: sbt.CommandSource): Exec = new Exec(commandLine, Option(execId), Option(source)) } diff --git a/main-command/src/main/contraband/state.contra b/main-command/src/main/contraband/state.contra index b319ecb56..929523505 100644 --- a/main-command/src/main/contraband/state.contra +++ b/main-command/src/main/contraband/state.contra @@ -3,7 +3,10 @@ package sbt type Exec { commandLine: String! + execId: String @since("0.0.1") source: sbt.CommandSource + + #xcompanion def newExecId: String = java.util.UUID.randomUUID.toString } type CommandSource { diff --git a/main-command/src/main/scala/sbt/BasicCommands.scala b/main-command/src/main/scala/sbt/BasicCommands.scala index caa1e0906..e0f744758 100644 --- a/main-command/src/main/scala/sbt/BasicCommands.scala +++ b/main-command/src/main/scala/sbt/BasicCommands.scala @@ -198,12 +198,9 @@ object BasicCommands { val s1 = exchange.run(s0) exchange.publishEvent(ConsolePromptEvent(s0)) val exec: Exec = exchange.blockUntilNextExec - val line = exec.commandLine - val source = exec.source - println(s"server (line, source): ($line, $source)") - val newState = s1.copy(onFailure = Some(Exec(Server, None)), remainingCommands = Exec(line, source) +: Exec(Server, None) +: s1.remainingCommands).setInteractive(true) - exchange.publishEvent(ConsoleUnpromptEvent(source)) - if (line.trim.isEmpty) newState + val newState = s1.copy(onFailure = Some(Exec(Server, None)), remainingCommands = exec +: Exec(Server, None) +: s1.remainingCommands).setInteractive(true) + exchange.publishEvent(ConsoleUnpromptEvent(exec.source)) + if (exec.commandLine.trim.isEmpty) newState else newState.clearGlobalLog } diff --git a/main-command/src/main/scala/sbt/Command.scala b/main-command/src/main/scala/sbt/Command.scala index 735ccf1af..13321ab2a 100644 --- a/main-command/src/main/scala/sbt/Command.scala +++ b/main-command/src/main/scala/sbt/Command.scala @@ -89,16 +89,18 @@ object Command { } /** This is the main function State transfer function of the sbt command processing, called by MainLoop.next, */ - def process(command: Exec, state: State): State = + def process(exec: Exec, state: State): State = { + val channelName = exec.source map { _.channelName } + State.exchange.publishEvent(ExecStatusEvent("Processing", channelName, exec.execId, Vector())) val parser = combine(state.definedCommands) - val newState = parse(command.commandLine, parser(state)) match { + val newState = parse(exec.commandLine, parser(state)) match { case Right(s) => s() // apply command. command side effects happen here case Left(errMsg) => state.log.error(errMsg) state.fail } - State.exchange.publishEvent(ExecStatusEvent("Ready", newState.remainingCommands.toVector map { _.commandLine })) + State.exchange.publishEvent(ExecStatusEvent("Done", channelName, exec.execId, newState.remainingCommands.toVector map { _.commandLine })) newState } def invalidValue(label: String, allowed: Iterable[String])(value: String): String = diff --git a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala index dd032b3c8..2a22b5b46 100644 --- a/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala +++ b/main-command/src/main/scala/sbt/internal/ConsoleChannel.scala @@ -19,8 +19,8 @@ private[sbt] final class ConsoleChannel(val name: String) extends CommandChannel // This internally handles thread interruption and returns Some("") val line = reader.readLine(prompt) line match { - case Some(cmd) => append(Exec(cmd, Some(CommandSource(name)))) - case None => append(Exec("exit", Some(CommandSource(name)))) + case Some(cmd) => append(Exec(cmd, Some(Exec.newExecId), Some(CommandSource(name)))) + case None => append(Exec("exit", Some(Exec.newExecId), Some(CommandSource(name)))) } askUserThread = None } diff --git a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala index 7a0798fdf..bab8fc137 100644 --- a/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala +++ b/main-command/src/main/scala/sbt/internal/client/NetworkClient.scala @@ -7,15 +7,18 @@ package client import java.net.{ URI, Socket, InetAddress, SocketException } import java.util.UUID +import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } import sbt.protocol._ import sbt.internal.util.JLine -import java.util.concurrent.atomic.{ AtomicBoolean, AtomicReference } +import scala.collection.mutable.ListBuffer class NetworkClient(arguments: List[String]) { self => private val channelName = new AtomicReference("_") private val status = new AtomicReference("Ready") private val lock: AnyRef = new AnyRef {} private val running = new AtomicBoolean(true) + private val pendingExecIds = ListBuffer.empty[String] + def usageError = sys.error("Expecting: sbt client 127.0.0.1:port") val connection = init() start() @@ -54,7 +57,14 @@ class NetworkClient(arguments: List[String]) { self => println(event) case e: ExecStatusEvent => status.set(e.status) - println(event) + // println(event) + e.execId foreach { execId => + if (e.status == "Done" && (pendingExecIds contains execId)) { + lock.synchronized { + pendingExecIds -= execId + } + } + } case e => println(e.toString) } @@ -68,7 +78,10 @@ class NetworkClient(arguments: List[String]) { self => case Some(s) => val execId = UUID.randomUUID.toString publishCommand(ExecCommand(s, execId)) - while (status.get != "Ready") { + lock.synchronized { + pendingExecIds += execId + } + while (pendingExecIds contains execId) { Thread.sleep(100) } case _ => // diff --git a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala index c1cef4db8..acdde4c50 100644 --- a/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala +++ b/main-command/src/main/scala/sbt/internal/server/NetworkChannel.scala @@ -65,7 +65,7 @@ final class NetworkChannel(val name: String, connection: Socket) extends Command def onCommand(command: CommandMessage): Unit = command match { - case x: ExecCommand => append(Exec(x.commandLine, Some(CommandSource(name)))) + case x: ExecCommand => append(Exec(x.commandLine, x.execId orElse Some(Exec.newExecId), Some(CommandSource(name)))) } def shutdown(): Unit = { diff --git a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala index 60b233a1d..f92b4bae9 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/ExecStatusEvent.scala @@ -7,31 +7,46 @@ package sbt.protocol /** Status event. */ final class ExecStatusEvent private ( val status: String, + val channelName: Option[String], + val execId: Option[String], val commandQueue: Vector[String]) extends sbt.protocol.EventMessage() with Serializable { override def equals(o: Any): Boolean = o match { - case x: ExecStatusEvent => (this.status == x.status) && (this.commandQueue == x.commandQueue) + case x: ExecStatusEvent => (this.status == x.status) && (this.channelName == x.channelName) && (this.execId == x.execId) && (this.commandQueue == x.commandQueue) case _ => false } override def hashCode: Int = { - 37 * (37 * (17 + status.##) + commandQueue.##) + 37 * (37 * (37 * (37 * (17 + status.##) + channelName.##) + execId.##) + commandQueue.##) } override def toString: String = { - "ExecStatusEvent(" + status + ", " + commandQueue + ")" + "ExecStatusEvent(" + status + ", " + channelName + ", " + execId + ", " + commandQueue + ")" } - protected[this] def copy(status: String = status, commandQueue: Vector[String] = commandQueue): ExecStatusEvent = { - new ExecStatusEvent(status, commandQueue) + protected[this] def copy(status: String = status, channelName: Option[String] = channelName, execId: Option[String] = execId, commandQueue: Vector[String] = commandQueue): ExecStatusEvent = { + new ExecStatusEvent(status, channelName, execId, commandQueue) } def withStatus(status: String): ExecStatusEvent = { copy(status = status) } + def withChannelName(channelName: Option[String]): ExecStatusEvent = { + copy(channelName = channelName) + } + def withChannelName(channelName: String): ExecStatusEvent = { + copy(channelName = Option(channelName)) + } + def withExecId(execId: Option[String]): ExecStatusEvent = { + copy(execId = execId) + } + def withExecId(execId: String): ExecStatusEvent = { + copy(execId = Option(execId)) + } def withCommandQueue(commandQueue: Vector[String]): ExecStatusEvent = { copy(commandQueue = commandQueue) } } object ExecStatusEvent { - def apply(status: String, commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, commandQueue) + def apply(status: String, channelName: Option[String], execId: Option[String], commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, channelName, execId, commandQueue) + def apply(status: String, channelName: String, execId: String, commandQueue: Vector[String]): ExecStatusEvent = new ExecStatusEvent(status, Option(channelName), Option(execId), commandQueue) } diff --git a/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala b/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala index 1772508b9..d95508389 100644 --- a/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala +++ b/protocol/src/main/contraband-scala/sbt/protocol/codec/ExecStatusEventFormats.scala @@ -12,9 +12,11 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent case Some(js) => unbuilder.beginObject(js) val status = unbuilder.readField[String]("status") + val channelName = unbuilder.readField[Option[String]]("channelName") + val execId = unbuilder.readField[Option[String]]("execId") val commandQueue = unbuilder.readField[Vector[String]]("commandQueue") unbuilder.endObject() - sbt.protocol.ExecStatusEvent(status, commandQueue) + sbt.protocol.ExecStatusEvent(status, channelName, execId, commandQueue) case None => deserializationError("Expected JsObject but found None") } @@ -22,6 +24,8 @@ implicit lazy val ExecStatusEventFormat: JsonFormat[sbt.protocol.ExecStatusEvent override def write[J](obj: sbt.protocol.ExecStatusEvent, builder: Builder[J]): Unit = { builder.beginObject() builder.addField("status", obj.status) + builder.addField("channelName", obj.channelName) + builder.addField("execId", obj.execId) builder.addField("commandQueue", obj.commandQueue) builder.endObject() } diff --git a/protocol/src/main/contraband/server.contra b/protocol/src/main/contraband/server.contra index 0702db439..06193441d 100644 --- a/protocol/src/main/contraband/server.contra +++ b/protocol/src/main/contraband/server.contra @@ -30,6 +30,8 @@ type LogEvent implements EventMessage { ## Status event. type ExecStatusEvent implements EventMessage { status: String! + channelName: String + execId: String commandQueue: [String] }