Skip to content

Commit

Permalink
#91 : Fixed Pull / Push progress bar
Browse files Browse the repository at this point in the history
We simply reuse the progressmeter as included in the JSON response. Strange feeling to let the Docker daemon render the progressmeter (should be a client functionality imo), but well, it works.

Fixes #91
  • Loading branch information
rhuss committed Apr 28, 2016
1 parent 18579a0 commit da5cc55
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 68 deletions.
3 changes: 3 additions & 0 deletions doc/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# ChangeLog

* **0.15.1-SNAPSHOT
- Fix push / pull progress bar (#91)

* **0.15.0** (2016-04-27)
- Be more conservative when no "warnings" are returned on create ([#407](https://github.com/fabric8io/docker-maven-plugin/issues/407))
- Fix parsing of timestamps with numeric timezone ([#410](https://github.com/fabric8io/docker-maven-plugin/issues/410))
Expand Down
2 changes: 1 addition & 1 deletion samples/data-jolokia-demo/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

<groupId>io.fabric8</groupId>
<artifactId>docker-jolokia-demo</artifactId>
<version>0.15.0</version>
<version>0.15.1-SNAPSHOT</version>
<!-- add custom lifecycle -->
<packaging>docker</packaging>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,11 @@ public void process(JSONObject json) throws DockerAccessException {
}
}
}

// Lifecycle methods not needed ...
@Override
public void start() {}

@Override
public void stop() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,27 @@ public class EntityStreamReaderUtil {
private EntityStreamReaderUtil() {}

public static void processJsonStream(JsonEntityResponseHandler handler, InputStream stream) throws IOException {
JSONTokener tokener = new JSONTokener(stream);
while (true) {
char next = tokener.nextClean();
if (next == 0) {
return;
} else {
tokener.back();
handler.start();
try {
JSONTokener tokener = new JSONTokener(stream);
while (true) {
char next = tokener.nextClean();
if (next == 0) {
return;
} else {
tokener.back();
}
JSONObject object = new JSONObject(tokener);
handler.process(object);
}
JSONObject object = new JSONObject(tokener);
handler.process(object);
} finally {
handler.stop();
}
}

public interface JsonEntityResponseHandler {
void process(JSONObject toProcess) throws DockerAccessException;
void start();
void stop();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

public class PullOrPushResponseJsonHandler implements EntityStreamReaderUtil.JsonEntityResponseHandler {

private boolean downloadInProgress = false;

private final Logger log;

public PullOrPushResponseJsonHandler(Logger log) {
Expand All @@ -17,30 +15,48 @@ public PullOrPushResponseJsonHandler(Logger log) {
@Override
public void process(JSONObject json) throws DockerAccessException {
if (json.has("progressDetail")) {
JSONObject details = json.getJSONObject("progressDetail");
if (details.has("total")) {
if (!downloadInProgress) {
log.progressStart(details.getInt("total"));
}
log.progressUpdate(details.getInt("current"));
downloadInProgress = true;
return;
}

if (downloadInProgress) {
log.progressFinished();
}
downloadInProgress = false;
log.progressUpdate(getStringOrEmpty(json, "id"),
getStringOrEmpty(json, "status"),
getStringOrEmpty(json, "progress"));
} else if (json.has("error")) {
throwDockerAccessException(json);
} else {
log.progressFinished();
logInfoMessage(json);
log.progressStart();
}
if (json.has("error")) {
String msg = json.getString("error").trim();
String details = json.getJSONObject("errorDetail").getString("message").trim();
throw new DockerAccessException("%s %s", msg, (msg.equals(details) ? "" : "(" + details + ")"));
}

private void logInfoMessage(JSONObject json) {
String value;
if (json.has("stream")) {
value = json.getString("stream").replaceFirst("\n$", "");
} else if (json.has("status")) {
value = json.getString("status");
} else {
if (json.length() > 0) {
log.info("... " + extractInfo(json));
}
value = json.toString();
}
log.info(value);
}

private void throwDockerAccessException(JSONObject json) throws DockerAccessException {
String msg = json.getString("error").trim();
String details = json.getJSONObject("errorDetail").getString("message").trim();
throw new DockerAccessException("%s %s", msg, (msg.equals(details) ? "" : "(" + details + ")"));
}

private String getStringOrEmpty(JSONObject json, String what) {
return json.has(what) ? json.getString(what) : "";
}

@Override
public void start() {
log.progressStart();
}

@Override
public void stop() {
log.progressFinished();
}

private String extractInfo(JSONObject json) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,12 +138,12 @@ private void setEntityIfGiven(HttpEntityEnclosingRequestBase request, Object ent
}
}

public static class StatusCodeCheckerResponseHandler<T> implements ResponseHandler<T> {
private static class StatusCodeCheckerResponseHandler<T> implements ResponseHandler<T> {

private int[] statusCodes;
private ResponseHandler<T> delegate;

public StatusCodeCheckerResponseHandler(ResponseHandler<T> delegate, int... statusCodes) {
StatusCodeCheckerResponseHandler(ResponseHandler<T> delegate, int... statusCodes) {
this.statusCodes = statusCodes;
this.delegate = delegate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,5 +444,4 @@ public Object handleResponse(HttpResponse response) throws IOException {
return null;
}
}

}
101 changes: 75 additions & 26 deletions src/main/java/io/fabric8/maven/docker/util/AnsiLogger.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package io.fabric8.maven.docker.util;

import java.util.HashMap;
import java.util.Map;

import org.apache.maven.plugin.logging.Log;
import org.codehaus.plexus.util.StringUtils;
import org.fusesource.jansi.Ansi;
import org.fusesource.jansi.AnsiConsole;

Expand All @@ -20,17 +24,25 @@ public class AnsiLogger implements Logger {

private final Log log;

private int oldProgress = 0;
private int total = 0;

private boolean verbose;

// ANSI escapes for various colors (or empty strings if no coloring is used)
private static Ansi.Color
COLOR_ERROR = RED,
COLOR_INFO = GREEN,
COLOR_WARNING = YELLOW,
COLOR_PROGRESS = CYAN;
COLOR_PROGRESS_ID = YELLOW,
COLOR_PROGRESS_STATUS = GREEN,
COLOR_PROGRESS_BAR = CYAN;

// Map remembering lines
private ThreadLocal<Map<String, Integer>> imageLines = new ThreadLocal<Map<String,Integer>>();

// Old image id when used in non ansi mode
private String oldImageId;

// Whether to use ANSI codes
private boolean useAnsi;

public AnsiLogger(Log log, boolean useColor, boolean verbose) {
this.log = log;
Expand Down Expand Up @@ -102,40 +114,79 @@ public boolean isDebugEnabled() {
*
* @param total the total number to be expected
*/
public void progressStart(int total) {
public void progressStart() {
// A progress indicator is always written out to standard out if a tty is enabled.
if (log.isInfoEnabled()) {
print(ansi().fg(COLOR_PROGRESS) + " ");
oldProgress = 0;
this.total = total;
imageLines.remove();
imageLines.set(new HashMap<String, Integer>());
oldImageId = null;
}
}

/**
* Update the progress
*
* @param current the current number to be expected
*/
public void progressUpdate(int current) {
if (log.isInfoEnabled()) {
print("=");
int newProgress = (current * 10 + 5) / total;
if (newProgress > oldProgress) {
print(" " + newProgress + "0% ");
oldProgress = newProgress;
public void progressUpdate(String layerId, String status, String progressMessage) {
if (log.isInfoEnabled() && StringUtils.isNotEmpty(layerId)) {
if (useAnsi) {
updateAnsiProgress(layerId, status, progressMessage);
} else {
updateNonAnsiProgress(layerId);
}
flush();
}
}

private void updateAnsiProgress(String imageId, String status, String progressMessage) {
Map<String,Integer> imgLineMap = imageLines.get();
Integer line = imgLineMap.get(imageId);

int diff = 0;
if (line == null) {
line = imgLineMap.size();
imgLineMap.put(imageId, line);
} else {
diff = imgLineMap.size() - line;
}

if (diff > 0) {
print(ansi().cursorUp(diff).eraseLine(Ansi.Erase.ALL).toString());
}

String progress = progressMessage != null ? progressMessage : "";
String msg =
ansi()
.fg(COLOR_PROGRESS_ID).a(imageId).reset().a(": ")
.fg(COLOR_PROGRESS_STATUS).a(status + " ")
.fg(COLOR_PROGRESS_BAR).a(progress).toString();
println(msg);

if (diff > 0) {
// move cursor back down to bottom
print(ansi().cursorDown(diff - 1).toString());
}
}

private void updateNonAnsiProgress(String imageId) {
if (!imageId.equals(oldImageId)) {
print("\n" + imageId + ": .");
oldImageId = imageId;
} else {
print(".");
}
}

/**
* Finis progress meter. Must be always called if {@link #progressStart(int)} has been used.
* Finis progress meter. Must be always called if {@link #progressStart()} has been used.
*/
public void progressFinished() {
if (log.isInfoEnabled()) {
println(ansi().reset().toString());
oldProgress = 0;
total = 0;
imageLines.remove();
oldImageId = null;
print(ansi().reset().toString());
if (!useAnsi) {
println("");
}
}
}

Expand All @@ -144,12 +195,10 @@ private void flush() {
}

private void initializeColor(boolean useColor) {
// sl4j simple logger used by Maven seems to escape ANSI escapes
if (System.console() == null || log.isDebugEnabled() || isWindows()) {
useColor = false;
}
// sl4j simple logger used by Maven seems to escape ANSI escapes on Windows
this.useAnsi = useColor && System.console() != null && !log.isDebugEnabled() && !isWindows();

if (useColor) {
if (useAnsi) {
AnsiConsole.systemInstall();
Ansi.setEnabled(true);
}
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/io/fabric8/maven/docker/util/Logger.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,20 +57,21 @@ public interface Logger {
boolean isDebugEnabled();

/**
* Start a progress bar
* @param total the total number to be expected
* Start a progress bar* @param total the total number to be expected
*/
void progressStart(int total);
void progressStart();

/**
* Update the progress
*
* @param current the current number to be expected
* @param layerId the image id of the layer fetched
* @param status a status message
* @param progressMessage the progressBar
*/
void progressUpdate(int current);
void progressUpdate(String layerId, String status, String progressMessage);

/**
* Finis progress meter. Must be always called if {@link #progressStart(int)} has been
* Finis progress meter. Must be always called if {@link #progressStart()} has been
* used.
*/
void progressFinished();
Expand Down

0 comments on commit da5cc55

Please sign in to comment.