Skip to content

Commit

Permalink
Send transfer events to the client for better display, fixes #284 (#313)
Browse files Browse the repository at this point in the history
  • Loading branch information
gnodet committed Jan 8, 2021
1 parent 0f438bd commit 28ffaea
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 9 deletions.
153 changes: 151 additions & 2 deletions common/src/main/java/org/mvndaemon/mvnd/common/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ public abstract class Message {
public static final int BUILD_STATUS = 14;
public static final int KEYBOARD_INPUT = 15;
public static final int CANCEL_BUILD = 16;
public static final int TRANSFER_INITIATED = 17;
public static final int TRANSFER_STARTED = 18;
public static final int TRANSFER_PROGRESSED = 19;
public static final int TRANSFER_CORRUPTED = 20;
public static final int TRANSFER_SUCCEEDED = 21;
public static final int TRANSFER_FAILED = 22;

public static final BareMessage KEEP_ALIVE_SINGLETON = new BareMessage(KEEP_ALIVE);
public static final BareMessage STOP_SINGLETON = new BareMessage(STOP);
Expand Down Expand Up @@ -93,6 +99,13 @@ public static Message read(DataInputStream input) throws IOException {
return StringMessage.read(type, input);
case CANCEL_BUILD:
return BareMessage.CANCEL_BUILD_SINGLETON;
case TRANSFER_INITIATED:
case TRANSFER_STARTED:
case TRANSFER_PROGRESSED:
case TRANSFER_CORRUPTED:
case TRANSFER_SUCCEEDED:
case TRANSFER_FAILED:
return TransferEvent.read(type, input);
}
throw new IllegalStateException("Unexpected message type: " + type);
}
Expand Down Expand Up @@ -760,6 +773,135 @@ public void write(DataOutputStream output) throws IOException {
}
}

public static class TransferEvent extends Message {

public static final int INITIATED = 0;
public static final int STARTED = 1;
public static final int PROGRESSED = 2;
public static final int CORRUPTED = 3;
public static final int SUCCEEDED = 4;
public static final int FAILED = 5;

public static final int GET = 0;
public static final int GET_EXISTENCE = 1;
public static final int PUT = 2;

final String projectId;
final int request;
final String repositoryId;
final String repositoryUrl;
final String resourceName;
final long contentLength;
final long transferredBytes;
final String exception;

private TransferEvent(int type, String projectId, int request,
String repositoryId, String repositoryUrl,
String resourceName, long contentLength, long transferredBytes,
String exception) {
super(type);
this.projectId = projectId;
this.request = request;
this.repositoryId = repositoryId;
this.repositoryUrl = repositoryUrl;
this.resourceName = resourceName;
this.contentLength = contentLength;
this.transferredBytes = transferredBytes;
this.exception = exception;
}

public String getProjectId() {
return projectId;
}

public int getRequest() {
return request;
}

public String getRepositoryId() {
return repositoryId;
}

public String getRepositoryUrl() {
return repositoryUrl;
}

public String getResourceName() {
return resourceName;
}

public long getContentLength() {
return contentLength;
}

public long getTransferredBytes() {
return transferredBytes;
}

public String getException() {
return exception;
}

@Override
public String toString() {
return mnemonic() + "{" +
"projectId=" + projectId +
", request=" + request +
", repositoryId='" + repositoryId + '\'' +
", repositoryUrl='" + repositoryUrl + '\'' +
", resourceName='" + resourceName + '\'' +
", contentLength=" + contentLength +
", transferredBytes=" + transferredBytes +
", exception='" + exception + '\'' +
'}';
}

private String mnemonic() {
switch (type) {
case TRANSFER_INITIATED:
return "TransferInitiated";
case TRANSFER_STARTED:
return "TransferStarted";
case TRANSFER_PROGRESSED:
return "TransferProgressed";
case TRANSFER_CORRUPTED:
return "TransferCorrupted";
case TRANSFER_SUCCEEDED:
return "TransferSucceeded";
case TRANSFER_FAILED:
return "TransferFailed";
default:
throw new IllegalStateException("Unexpected type " + type);
}
}

@Override
public void write(DataOutputStream output) throws IOException {
super.write(output);
writeUTF(output, projectId);
output.writeByte(request);
writeUTF(output, repositoryId);
writeUTF(output, repositoryUrl);
writeUTF(output, resourceName);
output.writeLong(contentLength);
output.writeLong(transferredBytes);
writeUTF(output, exception);
}

public static TransferEvent read(int type, DataInputStream input) throws IOException {
String projectId = readUTF(input);
int request = input.readByte();
String repositoryId = readUTF(input);
String repositoryUrl = readUTF(input);
String resourceName = readUTF(input);
long contentLength = input.readLong();
long transferredBytes = input.readLong();
String exception = readUTF(input);
return new TransferEvent(type, projectId, request, repositoryId, repositoryUrl, resourceName,
contentLength, transferredBytes, exception);
}
}

public int getType() {
return type;
}
Expand All @@ -785,7 +927,7 @@ public static StringMessage keyboardInput(char keyStroke) {
}

public static StringMessage projectStarted(String projectId) {
return new StringMessage(Message.PROJECT_STARTED, projectId);
return new StringMessage(PROJECT_STARTED, projectId);
}

public static StringMessage projectStopped(String projectId) {
Expand All @@ -795,11 +937,18 @@ public static StringMessage projectStopped(String projectId) {
public static Message mojoStarted(String artifactId, String pluginGroupId, String pluginArtifactId,
String pluginVersion, String mojo, String executionId) {
return new MojoStartedEvent(artifactId, pluginGroupId, pluginArtifactId, pluginVersion, mojo, executionId);

}

public static ProjectEvent display(String projectId, String message) {
return new ProjectEvent(Message.DISPLAY, projectId, message);
}

public static TransferEvent transfer(String projectId, int event, int request,
String repositoryId, String repositoryUrl,
String resourceName, long contentLength, long transferredBytes,
String exception) {
return new TransferEvent(event, projectId, request,
repositoryId, repositoryUrl, resourceName, contentLength, transferredBytes, exception);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import java.nio.file.Path;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
Expand All @@ -47,6 +49,8 @@
import org.mvndaemon.mvnd.common.Message.MojoStartedEvent;
import org.mvndaemon.mvnd.common.Message.ProjectEvent;
import org.mvndaemon.mvnd.common.Message.StringMessage;
import org.mvndaemon.mvnd.common.Message.TransferEvent;
import org.mvndaemon.mvnd.common.OsUtils;

/**
* A terminal {@link ClientOutput} based on JLine.
Expand All @@ -62,6 +66,7 @@ public class TerminalOutput implements ClientOutput {
private final Terminal terminal;
private final Terminal.SignalHandler previousIntHandler;
private final Display display;
private final Map<String, Map<String, TransferEvent>> transfers = new LinkedHashMap<>();
private final LinkedHashMap<String, Project> projects = new LinkedHashMap<>();
private final ClientLog log;
private final Thread reader;
Expand Down Expand Up @@ -353,13 +358,33 @@ private boolean doAccept(Message entry) {
}
break;
}
case Message.TRANSFER_INITIATED:
case Message.TRANSFER_STARTED:
case Message.TRANSFER_PROGRESSED: {
final TransferEvent te = (TransferEvent) entry;
transfers.computeIfAbsent(orEmpty(te.getProjectId()), p -> new LinkedHashMap<>())
.put(te.getResourceName(), te);
break;
}
case Message.TRANSFER_CORRUPTED:
case Message.TRANSFER_SUCCEEDED:
case Message.TRANSFER_FAILED: {
final TransferEvent te = (TransferEvent) entry;
transfers.computeIfAbsent(orEmpty(te.getProjectId()), p -> new LinkedHashMap<>())
.remove(te.getResourceName());
break;
}
default:
throw new IllegalStateException("Unexpected message " + entry);
}

return true;
}

private String orEmpty(String s) {
return s != null ? s : "";
}

private void applyNoBuffering() {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
projects.clear();
Expand Down Expand Up @@ -454,12 +479,22 @@ private void update() {
return;
}
final List<AttributedString> lines = new ArrayList<>(rows);
int dispLines = rows - 1; // for the build status line
dispLines--; // there's a bug which sometimes make the cursor goes one line below, so keep one more line empty
// at the end
final int projectsCount = projects.size();

int dispLines = rows;
// status line
dispLines--;
// there's a bug which sometimes make the cursor goes one line below,
// so keep one more line empty at the end
dispLines--;

AttributedString globalTransfer = formatTransfers("");
dispLines -= globalTransfer != null ? 1 : 0;

addStatusLine(lines, dispLines, projectsCount);
if (globalTransfer != null) {
lines.add(globalTransfer);
}

if (projectsCount <= dispLines) {
int remLogLines = dispLines - projectsCount;
Expand Down Expand Up @@ -490,6 +525,74 @@ private void update() {
display.update(trimmed, -1);
}

private AttributedString formatTransfers(String projectId) {
Collection<TransferEvent> transfers = this.transfers.getOrDefault(projectId, Collections.emptyMap()).values();
if (transfers.isEmpty()) {
return null;
}
TransferEvent event = transfers.iterator().next();
String action = event.getRequest() == TransferEvent.PUT ? "Uploading" : "Downloading";
if (transfers.size() == 1) {
String direction = event.getRequest() == TransferEvent.PUT ? "to" : "from";
long cur = event.getTransferredBytes();
long max = event.getContentLength();
String prg = OsUtils.kbTohumanReadable(cur / 1024) + " / " + OsUtils.kbTohumanReadable(max / 1024);
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(action);
asb.append(" ");
asb.style(AttributedStyle.BOLD);
asb.append(pathToMaven(event.getResourceName()));
asb.style(AttributedStyle.DEFAULT);
asb.append(" ");
asb.append(direction);
asb.append(" ");
asb.append(event.getRepositoryId());
if (cur > 0 && cur < max) {
asb.append(": ");
asb.append(prg);
}
return asb.toAttributedString();
} else {
return new AttributedString(action + " " + transfers.size() + " files...");
}
}

public static String pathToMaven(String location) {
String[] p = location.split("/");
if (p.length >= 4 && p[p.length - 1].startsWith(p[p.length - 3] + "-" + p[p.length - 2])) {
String artifactId = p[p.length - 3];
String version = p[p.length - 2];
String classifier;
String type;
String artifactIdVersion = artifactId + "-" + version;
StringBuilder sb = new StringBuilder();
if (p[p.length - 1].charAt(artifactIdVersion.length()) == '-') {
classifier = p[p.length - 1].substring(artifactIdVersion.length() + 1, p[p.length - 1].lastIndexOf('.'));
} else {
classifier = null;
}
type = p[p.length - 1].substring(p[p.length - 1].lastIndexOf('.') + 1);
for (int j = 0; j < p.length - 3; j++) {
if (j > 0) {
sb.append('.');
}
sb.append(p[j]);
}
sb.append(':').append(artifactId).append(':').append(version);
if (!"jar".equals(type) || classifier != null) {
sb.append(':');
if (!"jar".equals(type)) {
sb.append(type);
}
if (classifier != null) {
sb.append(':').append(classifier);
}
}
return sb.toString();
}
return location;
}

private void addStatusLine(final List<AttributedString> lines, int dispLines, final int projectsCount) {
if (name != null || buildStatus != null) {
AttributedStringBuilder asb = new AttributedStringBuilder();
Expand Down Expand Up @@ -548,7 +651,13 @@ private void addStatusLine(final List<AttributedString> lines, int dispLines, fi
private void addProjectLine(final List<AttributedString> lines, Project prj) {
final MojoStartedEvent execution = prj.runningExecution;
final AttributedStringBuilder asb = new AttributedStringBuilder();
if (execution == null) {
AttributedString transfer = formatTransfers(prj.id);
if (transfer != null) {
asb
.append(':')
.append(String.format(artifactIdFormat, prj.id))
.append(transfer);
} else if (execution == null) {
asb
.append(':')
.append(prj.id);
Expand Down
7 changes: 4 additions & 3 deletions daemon/src/main/java/org/apache/maven/cli/DaemonMavenCli.java
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
import org.mvndaemon.mvnd.logging.smart.BuildEventListener;
import org.mvndaemon.mvnd.logging.smart.LoggingExecutionListener;
import org.mvndaemon.mvnd.logging.smart.LoggingOutputStream;
import org.mvndaemon.mvnd.transfer.DaemonMavenTransferListener;
import org.slf4j.ILoggerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -1301,14 +1302,14 @@ protected TransferListener createTransferListener(CliRequest cliRequest) {
// If we're logging to a file then we don't want the console transfer listener as it will spew
// download progress all over the place
//
return getConsoleTransferListener(cliRequest.commandLine.hasOption(CLIManager.DEBUG));
return getDaemonTransferListener();
} else {
return getBatchTransferListener();
}
}

protected TransferListener getConsoleTransferListener(boolean printResourceNames) {
return new Slf4jMavenTransferListener(); // see https://github.com/mvndaemon/mvnd/issues/284
protected TransferListener getDaemonTransferListener() {
return new DaemonMavenTransferListener(buildEventListener);
}

protected TransferListener getBatchTransferListener() {
Expand Down

0 comments on commit 28ffaea

Please sign in to comment.