package uk.ac.rhul.cs.cl1.ui;
import java.io.PrintWriter;
import java.util.Arrays;
import uk.ac.rhul.cs.cl1.TaskMonitor;
import uk.ac.rhul.cs.utils.StringUtils;
/**
* A task monitor that shows the progress of a task on the console
* @author tamas
*/
public class ConsoleTaskMonitor implements TaskMonitor {
/** The progress of the task in percentages, or -1 if the task will be running indeterminately */
protected int percent = 0;
/** State of the spinner when the task is running indeterminately */
protected int spinnerState = 0;
/** The time of the last update of the progress bar.
*
* When in indeterminate state, this timestamp ensures that the progress bar is not updated
* too frequently.
*/
protected long lastUpdatedAt = -1;
/** Width of the progress bar in characters */
protected int progressBarWidth = 20;
/** The message shown on the console */
protected String message = "";
/** Whether the display is "dirty" (i.e., something has changed and it needs to be repainted */
protected boolean dirty = false;
/** Whether the display is drawn in the current line of the console */
protected boolean progressBarDrawn = false;
/** The width of the console as determined by {@link #getConsoleWidth()}, cached locally */
private Integer consoleWidth = null;
/** PrintWriter object that will be used to write to the standard error stream */
protected PrintWriter writer = new PrintWriter(System.err);
/**
* Returns the width of the progress bar shown on the screen
* @return the width in characters
*/
public int getProgressBarWidth() {
return progressBarWidth;
}
public void setEstimatedTimeRemaining(long time) {
// TODO
dirty = true;
}
public void setException(Throwable t, String userErrorMessage) {
this.setException(t, userErrorMessage, null);
}
public void setException(Throwable t, String userErrorMessage,
String recoveryTip) {
if (userErrorMessage != null && !userErrorMessage.isEmpty()) {
System.err.println("An unexpected error happened:");
System.err.println(userErrorMessage);
System.err.println();
System.err.println("The corresponding stack trace is:");
} else {
System.err.println("An unexpected exception happened:");
}
t.printStackTrace();
if (recoveryTip != null && !recoveryTip.isEmpty()) {
System.err.println();
System.err.println(recoveryTip);
}
}
public void setPercentCompleted(int percent)
throws IllegalArgumentException {
if (percent < -1 || percent > 100)
throw new IllegalArgumentException("percentage must be between -1 and 100");
if (percent == -1) {
this.percent = -1;
if (System.currentTimeMillis() - lastUpdatedAt > 100) {
this.spinnerState++;
this.dirty = true;
updateDisplay();
}
} else if (this.percent != percent) {
this.percent = percent;
this.dirty = true;
updateDisplay();
}
}
/**
* Sets the width of the progress bar shown on the screen
* @param width the new width
*/
public void setProgressBarWidth(int width) {
this.progressBarWidth = Math.max(1, progressBarWidth);
this.dirty = true;
}
public void setStatus(String message) {
if (!message.isEmpty() && !message.equals(this.message)) {
this.message = message;
this.dirty = true;
// Don't draw the progress bar if it is not drawn already in the current line;
// wait for the first setPercentCompleted() call instead
if (progressBarDrawn) {
updateDisplay();
}
}
}
/**
* Updates the progress display
*/
protected void updateDisplay() {
if (!this.dirty)
return;
char[] progress = new char[progressBarWidth];
Arrays.fill(progress, ' ');
writer.append('[');
if (percent >= 0) {
int numChars = (int)Math.round(progressBarWidth * percent / 100.0);
for (int i = 0; i < numChars; i++)
progress[i] = '=';
if (numChars > 0 && percent < 100)
progress[numChars-1] = '>';
} else {
spinnerState = spinnerState % progressBarWidth;
progress[spinnerState] = '|';
}
writer.write(progress);
writer.append(']');
if (percent >= 0)
writer.format("%4d%% ", percent);
else
writer.write(" ");
writer.append(StringUtils.substring(message, 0, getConsoleWidth() - progressBarWidth - 8));
if (percent == 100) {
writer.write("\r\n");
progressBarDrawn = false;
} else {
writer.append('\r');
progressBarDrawn = true;
}
writer.flush();
lastUpdatedAt = System.currentTimeMillis();
}
/**
* Tries to obtain the width of the console.
*
* This is an absolutely unsafe, non-portable and maybe not-even-working solution
* to determine the width of the console. It checks the environment variable named
* <tt>COLUMNS</tt> as it works under Linux. If no such environment variable is
* found, it simply returns 80, which is a safe default for most of the cases.
*/
private int getConsoleWidth() {
if (consoleWidth != null)
return consoleWidth;
consoleWidth = 80;
try {
if (System.getenv("COLUMNS") != null)
consoleWidth = Integer.parseInt(System.getenv("COLUMNS"));
} catch (NumberFormatException ex) {
/* well, meh */
}
return consoleWidth;
}
}