Skip to content
Permalink
Browse files

Show download progress. Closes #79

  • Loading branch information
mordechaim committed Feb 13, 2020
1 parent b5931e6 commit f182cf9812c6158724be6754b7dc7559f9f9a3d8
@@ -15,11 +15,21 @@
*/
package org.update4j.service;

import static org.update4j.util.StringUtils.formatSeconds;
import static org.update4j.util.StringUtils.humanReadableByteCount;
import static org.update4j.util.StringUtils.padLeft;
import static org.update4j.util.StringUtils.padRight;
import static org.update4j.util.StringUtils.repeat;

import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Timer;
import java.util.TimerTask;

import org.update4j.FileMetadata;
import org.update4j.UpdateContext;
import org.update4j.util.FileUtils;
import org.update4j.util.StringUtils;

public class DefaultUpdateHandler implements UpdateHandler {

@@ -35,60 +45,169 @@ public void init(UpdateContext context) {
this.context = context;
}

@Override
public void startDownloads() throws Throwable {
total = context.getRequiresUpdate().size();
ordinalWidth = String.valueOf(total).length() * 2 + 1;
initProgress();
}

@Override
public void doneCheckUpdateFile(FileMetadata file, boolean requires) throws Throwable {
System.out.print(compactName(context.getConfiguration().getBasePath(), file.getPath()));
if (requires) {
System.out.println(": UPDATE");
} else {
System.out.println(": SYNCHRONIZED");
}
public void startDownloadFile(FileMetadata file) throws Throwable {
index++;
println(renderFilename(file));
resetProgress(file.getSize());
}

@Override
public void updateDownloadFileProgress(FileMetadata file, float frac) throws Throwable {
currentFrac = frac;
}

@Override
public void startDownloadFile(FileMetadata file) throws Throwable {
// System.out.print("Downloading: " + compactName(context.getConfiguration().getBasePath(), file.getPath()) + " <"
// + file.getUri() + "> ");
System.out.println("Downloading: " + compactName(context.getConfiguration().getBasePath(), file.getPath())
+ " <" + file.getUri() + ">");
}

// private String percent;
//
// @Override
// public void updateDownloadFileProgress(FileMetadata file, float frac) throws InterruptedException {
// if (frac == 0) {
// System.out.print("(");
// }
//
// String percent = ((int) (frac * 100)) + "%) ";
// percent = percent.substring(0, 5);
//
// if (!percent.equals(this.percent)) {
// this.percent = percent;
// if (frac != 0)
// System.out.print("\b\b\b\b\b");
// System.out.print(percent);
// }
//
// if (frac == 1) {
// System.out.println();
// }
//
// }
public void doneDownloadFile(FileMetadata file, Path tempFile) throws Throwable {
clear();
}

@Override
public void failed(Throwable t) {
System.out.println();

clearln();
t.printStackTrace();
}

@Override
public void stop() {
stopTimer = true;
}

//------- Progress rendering, highly inspired by https://github.com/ctongfei/progressbar

private PrintStream out;
private Timer timer;

private int totalWidth;
private int msgWidth;
private int rateWidth;
private int percentWidth;
private int timeWidth;
private String clear;

private int ordinalWidth;
private int total;
private int index;

private long totalBytes;
private float lastFrac;
private float currentFrac;
private long start;
private boolean stopTimer;

private static String compactName(Path base, Path name) {
Path relative = FileUtils.relativize(base, name);
protected void initProgress() {
out = out();
totalWidth = consoleWidth();
msgWidth = "Downloading".length();
rateWidth = "@ 100.0 kB/s".length();
percentWidth = "100%".length();
timeWidth = "0:00:00".length();
clear = "\r" + repeat(totalWidth, " ") + "\r";

timer = new Timer("Progress Printer", true);
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if(stopTimer) {
timer.cancel();
return;
}

print(renderProgress());
lastFrac = currentFrac;
}
}, 0, 1000);
}

protected void resetProgress(long bytes) {
currentFrac = 0;
lastFrac = 0;
totalBytes = bytes;
start = System.currentTimeMillis();
}

protected PrintStream out() {
return System.out;
}

protected int consoleWidth() {
return 80;
}

public void clear() {
out.print(clear);
}

public void clearln() {
out.println(clear);
}

public void print(String str) {
out.print("\r");
out.print(padRight(totalWidth, str));
}

public void println(String str) {
out.print("\r");
out.println(padRight(totalWidth, str));
}

protected String renderProgress() {
StringBuilder sb = new StringBuilder();
sb.append("Downloading ");

String humanReadableBytes = humanReadableByteCount(totalBytes);

sb.append(humanReadableBytes);
sb.append(" ");
if(lastFrac == 0 && currentFrac == 0) {
sb.append(repeat(rateWidth + 1, " "));
} else {
sb.append("@ ");
sb.append(padRight(rateWidth - 2, humanReadableByteCount((long)((currentFrac - lastFrac) * totalBytes)) + "/s"));
sb.append(" ");
}
sb.append(padLeft(percentWidth, ((int) (currentFrac * 100)) + "%"));
sb.append(" [");

int progressWidth = totalWidth
- msgWidth
- humanReadableBytes.length()
- rateWidth
- percentWidth
- timeWidth
- 7; // spaces

int pieces = (int) ((progressWidth - 2) * currentFrac);
String line = repeat(pieces, "=");
if (pieces < progressWidth - 2)
line += ">";

sb.append(padRight(progressWidth - 2, line));
sb.append("]");

long elapsed = System.currentTimeMillis() - start;
if (currentFrac > 0) {
sb.append(" (");
sb.append(formatSeconds(((long) (elapsed / currentFrac) - elapsed) / 1000));
sb.append(")");
}

return sb.toString();
}

protected String renderFilename(FileMetadata file) {
return StringUtils.padLeft(ordinalWidth, index + "/" + total) + " " + compactName(file.getPath());
}

protected String compactName(Path name) {
Path relative = FileUtils.relativize(context.getConfiguration().getBasePath(), name);
return relative.isAbsolute() ? relative.getFileName().toString() : relative.toString();
}
}
@@ -15,6 +15,9 @@
*/
package org.update4j.util;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Set;

public class StringUtils {
@@ -107,4 +110,47 @@ public static String deriveModuleName(String filename) {
public static boolean isSystemModule(String str) {
return systemModules.contains(str);
}

public static String repeat(int n, String str) {
if(n < 0)
throw new IllegalArgumentException("n < 0: " + n);
// first lets try to use JDK 11's String::repeat
try {
Method repeat = String.class.getMethod("repeat", int.class);
return (String) repeat.invoke(str, n);
} catch (ReflectiveOperationException e) {
return String.join("", Collections.nCopies(n, str));
}
}

public static String padLeft(int width, String str) {
if(str.length() >= width)
return str;

return repeat(width - str.length(), " ") + str;
}

public static String padRight(int width, String str) {
if(str.length() >= width)
return str;

return str + repeat(width - str.length(), " ");
}

public static String formatSeconds(long m) {
return String.format("%d:%02d:%02d", m / 3600, (m % 3600) / 60, m % 60);
}

// https://stackoverflow.com/a/3758880/1751640
public static String humanReadableByteCount(long bytes) {
String s = bytes < 0 ? "-" : "";
long b = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
return b < 1000L ? bytes + " B"
: b < 999_950L ? String.format("%s%.1f kB", s, b / 1e3)
: (b /= 1000) < 999_950L ? String.format("%s%.1f MB", s, b / 1e3)
: (b /= 1000) < 999_950L ? String.format("%s%.1f GB", s, b / 1e3)
: (b /= 1000) < 999_950L ? String.format("%s%.1f TB", s, b / 1e3)
: (b /= 1000) < 999_950L ? String.format("%s%.1f PB", s, b / 1e3)
: String.format("%s%.1f EB", s, b / 1e6);
}
}

0 comments on commit f182cf9

Please sign in to comment.
You can’t perform that action at this time.