Client: have just one event queue and one consuming thread #133

This commit is contained in:
Peter Palaga
2020-11-09 19:56:43 +01:00
parent 243fceae9c
commit 03d6d49e7b
26 changed files with 704 additions and 671 deletions

View File

@@ -26,21 +26,34 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public abstract class Message {
static final int BUILD_REQUEST = 0;
static final int BUILD_EVENT = 1;
static final int BUILD_MESSAGE = 2;
static final int BUILD_EXCEPTION = 3;
static final int KEEP_ALIVE = 4;
static final int STOP = 5;
static final int BUILD_STARTED = 6;
static final int DISPLAY = 7;
static final int PROMPT = 8;
static final int PROMPT_RESPONSE = 9;
public static final int BUILD_REQUEST = 0;
public static final int BUILD_STARTED = 1;
public static final int BUILD_STOPPED = 2;
public static final int PROJECT_STARTED = 3;
public static final int PROJECT_STOPPED = 4;
public static final int MOJO_STARTED = 5;
public static final int BUILD_MESSAGE = 6;
public static final int BUILD_EXCEPTION = 7;
public static final int KEEP_ALIVE = 8;
public static final int STOP = 9;
public static final int DISPLAY = 10;
public static final int PROMPT = 11;
public static final int PROMPT_RESPONSE = 12;
public static final int BUILD_STATUS = 13;
public static final int KEYBOARD_INPUT = 14;
public static final SimpleMessage KEEP_ALIVE_SINGLETON = new SimpleMessage(Message.KEEP_ALIVE, "KEEP_ALIVE");
public static final SimpleMessage STOP_SINGLETON = new SimpleMessage(Message.STOP, "STOP");
public static final SimpleMessage KEEP_ALIVE_SINGLETON = new SimpleMessage(KEEP_ALIVE);
public static final SimpleMessage STOP_SINGLETON = new SimpleMessage(STOP);
public static final SimpleMessage BUILD_STOPPED_SINGLETON = new SimpleMessage(BUILD_STOPPED);
final int type;
Message(int type) {
this.type = type;
}
public static Message read(DataInputStream input) throws IOException {
int type = input.read();
@@ -52,8 +65,12 @@ public abstract class Message {
return BuildRequest.read(input);
case BUILD_STARTED:
return BuildStarted.read(input);
case BUILD_EVENT:
return BuildEvent.read(input);
case BUILD_STOPPED:
return SimpleMessage.BUILD_STOPPED_SINGLETON;
case PROJECT_STARTED:
case PROJECT_STOPPED:
case MOJO_STARTED:
return BuildEvent.read(type, input);
case BUILD_MESSAGE:
return BuildMessage.read(input);
case BUILD_EXCEPTION:
@@ -68,6 +85,8 @@ public abstract class Message {
return Prompt.read(input);
case PROMPT_RESPONSE:
return PromptResponse.read(input);
case BUILD_STATUS:
return StringMessage.read(BUILD_STATUS, input);
}
throw new IllegalStateException("Unexpected message type: " + type);
}
@@ -78,7 +97,9 @@ public abstract class Message {
return timestamp;
}
public abstract void write(DataOutputStream output) throws IOException;
public void write(DataOutputStream output) throws IOException {
output.write(type);
}
static void writeStringList(DataOutputStream output, List<String> value) throws IOException {
output.writeInt(value.size());
@@ -233,6 +254,7 @@ public abstract class Message {
}
public BuildRequest(List<String> args, String workingDir, String projectDir, Map<String, String> env) {
super(BUILD_REQUEST);
this.args = args;
this.workingDir = workingDir;
this.projectDir = projectDir;
@@ -266,7 +288,7 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(BUILD_REQUEST);
super.write(output);
writeStringList(output, args);
writeUTF(output, workingDir);
writeUTF(output, projectDir);
@@ -297,6 +319,7 @@ public abstract class Message {
}
public BuildException(String message, String className, String stackTrace) {
super(BUILD_EXCEPTION);
this.message = message;
this.className = className;
this.stackTrace = stackTrace;
@@ -325,7 +348,7 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(BUILD_EXCEPTION);
super.write(output);
writeUTF(output, message);
writeUTF(output, className);
writeUTF(output, stackTrace);
@@ -333,31 +356,21 @@ public abstract class Message {
}
public static class BuildEvent extends Message {
public enum Type {
BuildStopped, ProjectStarted, ProjectStopped, MojoStarted
}
final Type type;
final String projectId;
final String display;
public static Message read(DataInputStream input) throws IOException {
BuildEvent.Type type = BuildEvent.Type.values()[input.read()];
public static Message read(int type, DataInputStream input) throws IOException {
String projectId = readUTF(input);
String display = readUTF(input);
return new BuildEvent(type, projectId, display);
}
public BuildEvent(Type type, String projectId, String display) {
this.type = type;
public BuildEvent(int type, String projectId, String display) {
super(type);
this.projectId = projectId;
this.display = display;
}
public Type getType() {
return type;
}
public String getProjectId() {
return projectId;
}
@@ -368,17 +381,28 @@ public abstract class Message {
@Override
public String toString() {
return "BuildEvent{" +
return mnemonic() + "{" +
"projectId='" + projectId + '\'' +
", type=" + type +
", display='" + display + '\'' +
'}';
}
private String mnemonic() {
switch (type) {
case PROJECT_STARTED:
return "ProjectStarted";
case PROJECT_STOPPED:
return "ProjectStopped";
case MOJO_STARTED:
return "MojoStarted";
default:
throw new IllegalStateException("Unexpected type " + type);
}
}
@Override
public void write(DataOutputStream output) throws IOException {
output.write(BUILD_EVENT);
output.write(type.ordinal());
super.write(output);
writeUTF(output, projectId);
writeUTF(output, display);
}
@@ -398,6 +422,7 @@ public abstract class Message {
}
public BuildStarted(String projectId, int projectCount, int maxThreads) {
super(BUILD_STARTED);
this.projectId = projectId;
this.projectCount = projectCount;
this.maxThreads = maxThreads;
@@ -417,16 +442,14 @@ public abstract class Message {
@Override
public String toString() {
return "BuildEvent{" +
"projectId='" + projectId + '\'' +
", projectCount=" + projectCount +
", maxThreads='" + maxThreads + '\'' +
'}';
return "BuildStarted{" +
"projectId='" + projectId + "', projectCount=" + projectCount +
", maxThreads='" + maxThreads + "'}";
}
@Override
public void write(DataOutputStream output) throws IOException {
output.write(BUILD_STARTED);
super.write(output);
writeUTF(output, projectId);
output.writeInt(projectCount);
output.writeInt(maxThreads);
@@ -444,6 +467,7 @@ public abstract class Message {
}
public BuildMessage(String projectId, String message) {
super(BUILD_MESSAGE);
this.projectId = projectId;
this.message = message;
}
@@ -466,7 +490,7 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(BUILD_MESSAGE);
super.write(output);
writeUTF(output, projectId != null ? projectId : "");
writeUTF(output, message);
}
@@ -474,28 +498,66 @@ public abstract class Message {
public static class SimpleMessage extends Message {
final int type;
final String mnemonic;
/**
* Use {@link #KEEP_ALIVE_SINGLETON}
*
* @param type
*/
private SimpleMessage(int type, String mnemonic) {
this.type = type;
this.mnemonic = mnemonic;
private SimpleMessage(int type) {
super(type);
}
@Override
public String toString() {
return mnemonic;
switch (type) {
case KEEP_ALIVE:
return "KeepAlive";
case BUILD_STOPPED:
return "BuildStopped";
case STOP:
return "Stop";
default:
throw new IllegalStateException("Unexpected type " + type);
}
}
}
public static class StringMessage extends Message {
final String payload;
public static StringMessage read(int type, DataInputStream input) throws IOException {
String payload = readUTF(input);
return new StringMessage(type, payload);
}
private StringMessage(int type, String payload) {
super(type);
this.payload = payload;
}
public String getPayload() {
return payload;
}
@Override
public void write(DataOutputStream output) throws IOException {
output.write(type);
super.write(output);
writeUTF(output, payload);
}
@Override
public String toString() {
return mnemonic() + "{payload='" + payload + "'}";
}
private String mnemonic() {
switch (type) {
case BUILD_STATUS:
return "BuildStatus";
case Message.KEYBOARD_INPUT:
return "KeyboardInput";
default:
throw new IllegalStateException("Unexpected type " + type);
}
}
}
public static class Display extends Message {
@@ -510,6 +572,7 @@ public abstract class Message {
}
public Display(String projectId, String message) {
super(DISPLAY);
this.projectId = projectId;
this.message = message;
}
@@ -532,7 +595,7 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(DISPLAY);
super.write(output);
writeUTF(output, projectId);
writeUTF(output, message);
}
@@ -544,8 +607,9 @@ public abstract class Message {
final String uid;
final String message;
final boolean password;
final Consumer<String> callback;
public static Message read(DataInputStream input) throws IOException {
public static Prompt read(DataInputStream input) throws IOException {
String projectId = Message.readUTF(input);
String uid = Message.readUTF(input);
String message = Message.readUTF(input);
@@ -554,10 +618,16 @@ public abstract class Message {
}
public Prompt(String projectId, String uid, String message, boolean password) {
this(projectId, uid, message, password, null);
}
public Prompt(String projectId, String uid, String message, boolean password, Consumer<String> callback) {
super(PROMPT);
this.projectId = projectId;
this.uid = uid;
this.message = message;
this.password = password;
this.callback = callback;
}
public String getProjectId() {
@@ -588,12 +658,25 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(PROMPT);
super.write(output);
writeUTF(output, projectId);
writeUTF(output, uid);
writeUTF(output, message);
output.writeBoolean(password);
}
public Prompt withCallback(Consumer<String> callback) {
return new Prompt(projectId, uid, message, password, callback);
}
public PromptResponse response(String message) {
return new PromptResponse(projectId, uid, message);
}
public Consumer<String> getCallback() {
return callback;
}
}
public static class PromptResponse extends Message {
@@ -610,6 +693,7 @@ public abstract class Message {
}
public PromptResponse(String projectId, String uid, String message) {
super(PROMPT_RESPONSE);
this.projectId = projectId;
this.uid = uid;
this.message = message;
@@ -638,11 +722,31 @@ public abstract class Message {
@Override
public void write(DataOutputStream output) throws IOException {
output.write(PROMPT_RESPONSE);
super.write(output);
writeUTF(output, projectId);
writeUTF(output, uid);
writeUTF(output, message);
}
}
public int getType() {
return type;
}
public static StringMessage buildStatus(String payload) {
return new StringMessage(BUILD_STATUS, payload);
}
public static BuildMessage log(String message) {
return new BuildMessage(null, message);
}
public static BuildMessage log(String projectId, String message) {
return new BuildMessage(projectId, message);
}
public static StringMessage keyboardInput(char keyStroke) {
return new StringMessage(KEYBOARD_INPUT, String.valueOf(keyStroke));
}
}

View File

@@ -15,28 +15,16 @@
*/
package org.jboss.fuse.mvnd.common.logging;
import java.util.List;
import org.jboss.fuse.mvnd.common.Message;
/**
* A sink for various kinds of events sent by the daemon.
*/
public interface ClientOutput extends AutoCloseable {
void accept(Message message);
void startBuild(String name, int projects, int cores);
void projectStateChanged(String projectId, String display);
void projectFinished(String projectId);
void accept(String projectId, String message);
void error(String message, String className, String stackTrace);
void keepAlive();
void buildStatus(String status);
void display(String projectId, String message);
String prompt(String projectId, String message, boolean password);
void accept(List<Message> messages);
void describeTerminal();
}

View File

@@ -27,16 +27,20 @@ import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import org.jboss.fuse.mvnd.common.Message;
import org.jboss.fuse.mvnd.common.Message.BuildEvent;
import org.jboss.fuse.mvnd.common.Message.BuildException;
import org.jboss.fuse.mvnd.common.Message.BuildMessage;
import org.jboss.fuse.mvnd.common.Message.BuildStarted;
import org.jboss.fuse.mvnd.common.Message.SimpleMessage;
import org.jboss.fuse.mvnd.common.Message.StringMessage;
import org.jline.terminal.Size;
import org.jline.terminal.Terminal;
import org.jline.terminal.TerminalBuilder;
@@ -55,12 +59,10 @@ public class TerminalOutput implements ClientOutput {
public static final int CTRL_M = 'M' & 0x1f;
private final BlockingQueue<Event> queue;
private final Terminal terminal;
private final Display display;
private final LinkedHashMap<String, Project> projects = new LinkedHashMap<>();
private final ClientLog log;
private final Thread worker;
private final Thread reader;
private volatile Exception exception;
private volatile boolean closing;
@@ -77,39 +79,6 @@ public class TerminalOutput implements ClientOutput {
private String buildStatus; // read/written only by the displayLoop
private boolean displayDone = false; // read/written only by the displayLoop
enum EventType {
BUILD_STATUS,
PROJECT_STATE,
PROJECT_FINISHED,
LOG,
ERROR,
END_OF_STREAM,
INPUT,
KEEP_ALIVE,
DISPLAY,
PROMPT,
PROMPT_PASSWORD
}
static class Event {
public static final Event KEEP_ALIVE = new Event(EventType.KEEP_ALIVE, null, null);
public final EventType type;
public final String projectId;
public final String message;
public final SynchronousQueue<String> response;
public Event(EventType type, String projectId, String message) {
this(type, projectId, message, null);
}
public Event(EventType type, String projectId, String message, SynchronousQueue<String> response) {
this.type = type;
this.projectId = projectId;
this.message = message;
this.response = response;
}
}
/**
* {@link Project} is owned by the display loop thread and is accessed only from there. Therefore it does not need
* to be immutable.
@@ -126,113 +95,177 @@ public class TerminalOutput implements ClientOutput {
public TerminalOutput(Path logFile) throws IOException {
this.start = System.currentTimeMillis();
this.queue = new LinkedBlockingDeque<>();
this.terminal = TerminalBuilder.terminal();
terminal.enterRawMode();
this.display = new Display(terminal, false);
this.log = logFile == null ? new MessageCollector() : new FileLog(logFile);
final Thread w = new Thread(this::displayLoop);
w.start();
this.worker = w;
final Thread r = new Thread(this::readInputLoop);
r.start();
this.reader = r;
}
public void startBuild(String name, int projects, int cores) {
this.name = name;
this.totalProjects = projects;
this.maxThreads = cores;
try {
queue.put(Event.KEEP_ALIVE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void projectStateChanged(String projectId, String task) {
try {
queue.put(new Event(EventType.PROJECT_STATE, projectId, task));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
public void projectFinished(String projectId) {
try {
queue.put(new Event(EventType.PROJECT_FINISHED, projectId, null));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
@Override
public void accept(Message entry) {
assert "main".equals(Thread.currentThread().getName());
doAccept(entry);
update();
}
@Override
public void accept(String projectId, String message) {
try {
if (closing) {
closed.await();
System.err.println(message);
public void accept(List<Message> entries) {
assert "main".equals(Thread.currentThread().getName());
for (Message entry : entries) {
doAccept(entry);
}
update();
}
private void doAccept(Message entry) {
switch (entry.getType()) {
case Message.BUILD_STARTED: {
BuildStarted bs = (BuildStarted) entry;
this.name = bs.getProjectId();
this.totalProjects = bs.getProjectCount();
this.maxThreads = bs.getMaxThreads();
break;
}
case Message.BUILD_EXCEPTION: {
final BuildException e = (BuildException) entry;
final String msg;
if ("org.apache.commons.cli.UnrecognizedOptionException".equals(e.getClassName())) {
msg = "Unable to parse command line options: " + e.getMessage();
} else {
queue.put(new Event(EventType.LOG, projectId, message));
msg = e.getClassName() + ": " + e.getMessage();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
clearDisplay();
try {
log.close();
} catch (IOException e1) {
throw new RuntimeException(e1);
}
final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
new AttributedString(msg, s).println(terminal);
terminal.flush();
return;
}
}
case Message.PROJECT_STARTED:
case Message.MOJO_STARTED: {
BuildEvent be = (BuildEvent) entry;
Project prj = projects.computeIfAbsent(be.getProjectId(), Project::new);
prj.status = be.getDisplay();
break;
}
case Message.PROJECT_STOPPED: {
BuildEvent be = (BuildEvent) entry;
Project prj = projects.remove(be.getProjectId());
if (prj != null) {
prj.log.forEach(log);
}
doneProjects++;
displayDone();
break;
}
case Message.BUILD_STATUS: {
this.buildStatus = ((StringMessage) entry).getPayload();
break;
}
case Message.BUILD_STOPPED: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
clearDisplay();
try {
log.close();
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
terminal.flush();
}
return;
}
case Message.KEEP_ALIVE: {
break;
}
case Message.DISPLAY: {
Message.Display d = (Message.Display) entry;
display.update(Collections.emptyList(), 0);
terminal.writer().printf("[%s] %s%n", d.getProjectId(), d.getMessage());
break;
}
case Message.PROMPT: {
Message.Prompt prompt = (Message.Prompt) entry;
@Override
public void error(String message, String className, String stackTrace) {
final String msg;
if ("org.apache.commons.cli.UnrecognizedOptionException".equals(className)) {
msg = "Unable to parse command line options: " + message;
} else {
msg = className + ": " + message;
readInput.writeLock().lock();
try {
display.update(Collections.emptyList(), 0);
terminal.writer().printf("[%s] %s", prompt.getProjectId(), prompt.getMessage());
terminal.flush();
StringBuilder sb = new StringBuilder();
while (true) {
int c = terminal.reader().read();
if (c < 0) {
break;
} else if (c == '\n' || c == '\r') {
prompt.getCallback().accept(sb.toString());
terminal.writer().println();
break;
} else if (c == 127) {
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
terminal.writer().write("\b \b");
terminal.writer().flush();
}
} else {
terminal.writer().print((char) c);
terminal.writer().flush();
sb.append((char) c);
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
readInput.writeLock().unlock();
}
break;
}
try {
queue.put(new Event(EventType.ERROR, null, msg));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
case Message.BUILD_MESSAGE: {
BuildMessage bm = (BuildMessage) entry;
if (closing) {
try {
closed.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.err.println(bm.getMessage());
} else {
if (bm.getProjectId() != null) {
Project prj = projects.computeIfAbsent(bm.getProjectId(), Project::new);
prj.log.add(bm.getMessage());
} else {
log.accept(bm.getMessage());
}
}
break;
}
case Message.KEYBOARD_INPUT:
char keyStroke = ((StringMessage) entry).getPayload().charAt(0);
switch (keyStroke) {
case '+':
linesPerProject = Math.min(10, linesPerProject + 1);
break;
case '-':
linesPerProject = Math.max(0, linesPerProject - 1);
break;
case CTRL_L:
display.update(Collections.emptyList(), 0);
break;
case CTRL_M:
displayDone = !displayDone;
displayDone();
break;
}
break;
}
}
@Override
public void keepAlive() {
try {
queue.put(Event.KEEP_ALIVE);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void buildStatus(String status) {
try {
queue.put(new Event(EventType.BUILD_STATUS, null, status));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void display(String projectId, String message) {
try {
queue.put(new Event(EventType.DISPLAY, projectId, message));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public String prompt(String projectId, String message, boolean password) {
String response = null;
try {
SynchronousQueue<String> sq = new SynchronousQueue<>();
queue.put(new Event(password ? EventType.PROMPT_PASSWORD : EventType.PROMPT, projectId, message, sq));
response = sq.take();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return response;
}
@Override
@@ -242,7 +275,7 @@ public class TerminalOutput implements ClientOutput {
if (terminal instanceof AbstractPosixTerminal) {
sb.append(" with pty ").append(((AbstractPosixTerminal) terminal).getPty().getClass().getName());
}
this.accept(null, sb.toString());
this.accept(Message.log(sb.toString()));
}
void readInputLoop() {
@@ -254,7 +287,7 @@ public class TerminalOutput implements ClientOutput {
break;
}
if (c == '+' || c == '-' || c == CTRL_L || c == CTRL_M) {
queue.add(new Event(EventType.INPUT, null, Character.toString((char) c)));
accept(Message.keyboardInput((char) c));
}
readInput.readLock().unlock();
}
@@ -268,130 +301,17 @@ public class TerminalOutput implements ClientOutput {
}
}
void displayLoop() {
final List<Event> entries = new ArrayList<>();
while (true) {
try {
entries.add(queue.take());
queue.drainTo(entries);
for (Event entry : entries) {
switch (entry.type) {
case BUILD_STATUS: {
this.buildStatus = entry.message;
break;
}
case END_OF_STREAM: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
clearDisplay();
log.close();
terminal.flush();
return;
}
case LOG: {
if (entry.projectId != null) {
Project prj = projects.computeIfAbsent(entry.projectId, Project::new);
prj.log.add(entry.message);
} else {
log.accept(entry.message);
}
break;
}
case ERROR: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
clearDisplay();
log.close();
final AttributedStyle s = new AttributedStyle().bold().foreground(AttributedStyle.RED);
new AttributedString(entry.message, s).println(terminal);
terminal.flush();
return;
}
case PROJECT_STATE: {
Project prj = projects.computeIfAbsent(entry.projectId, Project::new);
prj.status = entry.message;
break;
}
case PROJECT_FINISHED: {
Project prj = projects.remove(entry.projectId);
if (prj != null) {
prj.log.forEach(log);
}
doneProjects++;
displayDone();
break;
}
case INPUT:
switch (entry.message.charAt(0)) {
case '+':
linesPerProject = Math.min(10, linesPerProject + 1);
break;
case '-':
linesPerProject = Math.max(0, linesPerProject - 1);
break;
case CTRL_L:
display.update(Collections.emptyList(), 0);
break;
case CTRL_M:
displayDone = !displayDone;
displayDone();
break;
}
break;
case DISPLAY:
display.update(Collections.emptyList(), 0);
terminal.writer().printf("[%s] %s%n", entry.projectId, entry.message);
break;
case PROMPT:
case PROMPT_PASSWORD: {
readInput.writeLock().lock();
try {
display.update(Collections.emptyList(), 0);
terminal.writer().printf("[%s] %s", entry.projectId, entry.message);
terminal.flush();
StringBuilder sb = new StringBuilder();
while (true) {
int c = terminal.reader().read();
if (c < 0) {
break;
} else if (c == '\n' || c == '\r') {
entry.response.put(sb.toString());
terminal.writer().println();
break;
} else if (c == 127) {
if (sb.length() > 0) {
sb.setLength(sb.length() - 1);
terminal.writer().write("\b \b");
terminal.writer().flush();
}
} else {
terminal.writer().print((char) c);
terminal.writer().flush();
sb.append((char) c);
}
}
} finally {
readInput.writeLock().unlock();
}
}
}
}
entries.clear();
update();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
this.exception = e;
}
}
}
private void clearDisplay() {
display.update(Collections.emptyList(), 0);
}
private void displayDone() throws IOException {
private void displayDone() {
if (displayDone) {
log.flush();
try {
log.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
@@ -399,8 +319,7 @@ public class TerminalOutput implements ClientOutput {
public void close() throws Exception {
closing = true;
reader.interrupt();
queue.put(new Event(EventType.END_OF_STREAM, null, null));
worker.join();
accept(SimpleMessage.BUILD_STOPPED_SINGLETON);
reader.join();
terminal.close();
closed.countDown();
@@ -456,39 +375,41 @@ public class TerminalOutput implements ClientOutput {
}
private void addStatusLine(final List<AttributedString> lines, int dispLines, final int projectsCount) {
AttributedStringBuilder asb = new AttributedStringBuilder();
StringBuilder statusLine = new StringBuilder(64);
if (name == null) {
statusLine.append(buildStatus != null ? buildStatus : "Looking up daemon...");
} else {
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
asb.append(name);
asb.style(AttributedStyle.DEFAULT);
if (projectsCount <= dispLines) {
statusLine.append(" threads used/max: ")
.append(projectsCount).append('/').append(maxThreads);
if (name != null || buildStatus != null) {
AttributedStringBuilder asb = new AttributedStringBuilder();
StringBuilder statusLine = new StringBuilder(64);
if (name != null) {
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
asb.append(name);
asb.style(AttributedStyle.DEFAULT);
if (projectsCount <= dispLines) {
statusLine.append(" threads used/max: ")
.append(projectsCount).append('/').append(maxThreads);
} else {
statusLine.append(" threads used/hidden/max: ")
.append(projectsCount).append('/').append(projectsCount - dispLines).append('/').append(maxThreads);
}
if (totalProjects > 0) {
statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ')
.append(doneProjects * 100 / totalProjects).append('%');
}
} else if (buildStatus != null) {
statusLine.append(buildStatus);
}
statusLine.append(" time: ");
long sec = (System.currentTimeMillis() - this.start) / 1000;
if (sec > 60) {
statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s');
} else {
statusLine.append(" threads used/hidden/max: ")
.append(projectsCount).append('/').append(projectsCount - dispLines).append('/').append(maxThreads);
statusLine.append(sec).append('s');
}
if (totalProjects > 0) {
statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ')
.append(doneProjects * 100 / totalProjects).append('%');
}
asb.append(statusLine.toString());
lines.add(asb.toAttributedString());
}
statusLine.append(" time: ");
long sec = (System.currentTimeMillis() - this.start) / 1000;
if (sec > 60) {
statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s');
} else {
statusLine.append(sec).append('s');
}
asb.append(statusLine.toString());
lines.add(asb.toAttributedString());
}
private void addProjectLine(final List<AttributedString> lines, Project prj) {