Skip to content

Commit d4ca6ce

Browse files
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).
1 parent b032f74 commit d4ca6ce

File tree

2 files changed

+54
-11
lines changed

2 files changed

+54
-11
lines changed

app/src/cc/arduino/packages/Uploader.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ protected boolean executeUploadCommand(String command[]) throws Exception {
102102
System.out.println();
103103
}
104104
Process process = ProcessUtils.exec(command);
105-
new MessageSiphon(process.getInputStream(), this);
106-
new MessageSiphon(process.getErrorStream(), this);
105+
new MessageSiphon(process.getInputStream(), this, 100);
106+
new MessageSiphon(process.getErrorStream(), this, 100);
107107

108108
// wait for the process to finish.
109109
result = process.waitFor();

app/src/processing/app/debug/MessageSiphon.java

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,26 +23,35 @@
2323

2424
package processing.app.debug;
2525

26-
import java.io.BufferedReader;
2726
import java.io.InputStream;
2827
import java.io.InputStreamReader;
28+
import java.io.Reader;
2929
import java.net.SocketException;
3030

3131
/**
3232
* Slurps up messages from compiler.
3333
*/
3434
public class MessageSiphon implements Runnable {
3535

36-
private final BufferedReader streamReader;
36+
private final Reader streamReader;
3737
private final MessageConsumer consumer;
3838

3939
private Thread thread;
4040
private boolean canRun;
41+
// Data is processed line-by-line if possible, but if this is non-zero
42+
// then a partial line is also processed if no line end is received
43+
// within this many milliseconds.
44+
private int lineTimeout;
4145

4246
public MessageSiphon(InputStream stream, MessageConsumer consumer) {
43-
this.streamReader = new BufferedReader(new InputStreamReader(stream));
47+
this(stream, consumer, 0);
48+
}
49+
50+
public MessageSiphon(InputStream stream, MessageConsumer consumer, int lineTimeout) {
51+
this.streamReader = new InputStreamReader(stream);
4452
this.consumer = consumer;
4553
this.canRun = true;
54+
this.lineTimeout = lineTimeout;
4655

4756
thread = new Thread(this);
4857
// don't set priority too low, otherwise exceptions won't
@@ -59,12 +68,46 @@ public void run() {
5968
// (effectively sleeping the thread) until new data comes in.
6069
// when the program is finally done, null will come through.
6170
//
62-
String currentLine;
63-
while (canRun && (currentLine = streamReader.readLine()) != null) {
64-
// \n is added again because readLine() strips it out
65-
//EditorConsole.systemOut.println("messaging in");
66-
consumer.message(currentLine + "\n");
67-
//EditorConsole.systemOut.println("messaging out");
71+
StringBuilder currentLine = new StringBuilder();
72+
long lineStartTime = 0;
73+
while (canRun) {
74+
// First, try to read as many characters as possible. Take care
75+
// not to block when:
76+
// 1. lineTimeout is nonzero, and
77+
// 2. we have some characters buffered already
78+
while (lineTimeout == 0 || currentLine.length() == 0 || streamReader.ready()) {
79+
int c = streamReader.read();
80+
if (c == -1)
81+
return; // EOF
82+
if (!canRun)
83+
return;
84+
85+
// Keep track of the line start time
86+
if (currentLine.length() == 0)
87+
lineStartTime = System.nanoTime();
88+
89+
// Store the character line
90+
currentLine.append((char)c);
91+
92+
if (c == '\n') {
93+
// We read a full line, pass it on
94+
consumer.message(currentLine.toString());
95+
currentLine.setLength(0);
96+
}
97+
}
98+
99+
// No more characters available. Wait until lineTimeout
100+
// milliseconds have passed since the start of the line and then
101+
// try reading again. If the time has already passed, then just
102+
// pass on the characters read so far.
103+
long passed = (System.nanoTime() - lineStartTime) / 1000;
104+
if (passed < this.lineTimeout) {
105+
Thread.sleep(this.lineTimeout - passed);
106+
continue;
107+
}
108+
109+
consumer.message(currentLine.toString());
110+
currentLine.setLength(0);
68111
}
69112
//EditorConsole.systemOut.println("messaging thread done");
70113
} catch (NullPointerException npe) {

0 commit comments

Comments
 (0)