From d4ca6ce1270cef16a04c04000587a8ebe9a946a9 Mon Sep 17 00:00:00 2001 From: Matthijs Kooijman Date: Thu, 10 Apr 2014 11:33:23 +0200 Subject: [PATCH] Allow setting a line timeout in MessageSiphon Previously, the MessageSiphon class would read characters from an InputStream and then push them to the passed MessageConsumer one line at a time. Now, you can specify a line timeout. Normally, messages are still processed line by line, but if no line ending is received within the specified timeout (counting from the first character in the line), then the incomplete line is passed on as a message, without waiting for the line ending. This feature is used for the uploader command output. In particular, this allows the avrdude progress bar to be shown in the IDE as expected, character by character (previously, the entire progress bar would be buffered, making it show up completely at the end of the upload). --- app/src/cc/arduino/packages/Uploader.java | 4 +- .../processing/app/debug/MessageSiphon.java | 61 ++++++++++++++++--- 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/app/src/cc/arduino/packages/Uploader.java b/app/src/cc/arduino/packages/Uploader.java index e8e38edfe..012a18418 100644 --- a/app/src/cc/arduino/packages/Uploader.java +++ b/app/src/cc/arduino/packages/Uploader.java @@ -102,8 +102,8 @@ public abstract class Uploader implements MessageConsumer { System.out.println(); } Process process = ProcessUtils.exec(command); - new MessageSiphon(process.getInputStream(), this); - new MessageSiphon(process.getErrorStream(), this); + new MessageSiphon(process.getInputStream(), this, 100); + new MessageSiphon(process.getErrorStream(), this, 100); // wait for the process to finish. result = process.waitFor(); diff --git a/app/src/processing/app/debug/MessageSiphon.java b/app/src/processing/app/debug/MessageSiphon.java index 26901c3f4..c9abf0ea7 100644 --- a/app/src/processing/app/debug/MessageSiphon.java +++ b/app/src/processing/app/debug/MessageSiphon.java @@ -23,9 +23,9 @@ package processing.app.debug; -import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.Reader; import java.net.SocketException; /** @@ -33,16 +33,25 @@ import java.net.SocketException; */ public class MessageSiphon implements Runnable { - private final BufferedReader streamReader; + private final Reader streamReader; private final MessageConsumer consumer; private Thread thread; private boolean canRun; + // Data is processed line-by-line if possible, but if this is non-zero + // then a partial line is also processed if no line end is received + // within this many milliseconds. + private int lineTimeout; public MessageSiphon(InputStream stream, MessageConsumer consumer) { - this.streamReader = new BufferedReader(new InputStreamReader(stream)); + this(stream, consumer, 0); + } + + public MessageSiphon(InputStream stream, MessageConsumer consumer, int lineTimeout) { + this.streamReader = new InputStreamReader(stream); this.consumer = consumer; this.canRun = true; + this.lineTimeout = lineTimeout; thread = new Thread(this); // don't set priority too low, otherwise exceptions won't @@ -59,12 +68,46 @@ public class MessageSiphon implements Runnable { // (effectively sleeping the thread) until new data comes in. // when the program is finally done, null will come through. // - String currentLine; - while (canRun && (currentLine = streamReader.readLine()) != null) { - // \n is added again because readLine() strips it out - //EditorConsole.systemOut.println("messaging in"); - consumer.message(currentLine + "\n"); - //EditorConsole.systemOut.println("messaging out"); + StringBuilder currentLine = new StringBuilder(); + long lineStartTime = 0; + while (canRun) { + // First, try to read as many characters as possible. Take care + // not to block when: + // 1. lineTimeout is nonzero, and + // 2. we have some characters buffered already + while (lineTimeout == 0 || currentLine.length() == 0 || streamReader.ready()) { + int c = streamReader.read(); + if (c == -1) + return; // EOF + if (!canRun) + return; + + // Keep track of the line start time + if (currentLine.length() == 0) + lineStartTime = System.nanoTime(); + + // Store the character line + currentLine.append((char)c); + + if (c == '\n') { + // We read a full line, pass it on + consumer.message(currentLine.toString()); + currentLine.setLength(0); + } + } + + // No more characters available. Wait until lineTimeout + // milliseconds have passed since the start of the line and then + // try reading again. If the time has already passed, then just + // pass on the characters read so far. + long passed = (System.nanoTime() - lineStartTime) / 1000; + if (passed < this.lineTimeout) { + Thread.sleep(this.lineTimeout - passed); + continue; + } + + consumer.message(currentLine.toString()); + currentLine.setLength(0); } //EditorConsole.systemOut.println("messaging thread done"); } catch (NullPointerException npe) {