Skip to content

Commit 8131de4

Browse files
authored
Merge pull request #12 from CSC207-2022F-UofT/feat-progress-bar
[+] Progress Bar
2 parents da69764 + 7f9c2e8 commit 8131de4

File tree

7 files changed

+398
-39
lines changed

7 files changed

+398
-39
lines changed

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,28 @@ Final Project for CSC207
99
* [Repo in CSC207 Organization](https://github.com/CSC207-2022F-UofT/mcpm)
1010
* [File Server Backend](https://mcpm.hydev.org)
1111

12+
## Development
13+
14+
### Run a specific class in an external terminal
15+
16+
This is very useful to test terminal operations since the Gradle running environment isn't a tty, and IntelliJ IDEA's built-in terminal barely supports Xterm escape sequences.
17+
18+
For this, I've set up a custom gradle task `printCp` that will print out the classpath needed to run the classes with dependencies. It will print in stderr instead of stdout in order for bash to easily separate out the classpath. You can obtain the classpath in a bash variable by:
19+
20+
`cp="$(./gradlew classes testClasses printCp 2>&1 > /dev/null)" && echo "$cp"`
21+
22+
(Unfortunately since Windows doesn't support Bash, you'll need to use a Bash-compatible environment on Windows, either cygwin / git bash or WSL)
23+
24+
Then, you can run your class with:
25+
26+
`java19 -cp "$cp" org.hydev.mcpm.<class>`
27+
28+
For example, you can test the progress bar with:
29+
30+
`java19 -cp "$cp" org.hydev.mcpm.client.interaction.ProgressBar`
31+
32+
If you don't have JDK 19 installed or if you don't know where it's installed, you can use our JDK downloader tool to download a local version of JDK 19 without installing on the system. (TODO: Add tutorial after merging PR #8)
33+
1234
## Brainstorm
1335

1436
Server file/endpoint structure:

build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ dependencies {
4040
// https://mvnrepository.com/artifact/org.jetbrains/annotations
4141
implementation 'org.jetbrains:annotations:16.0.1'
4242

43+
// https://mvnrepository.com/artifact/org.fusesource.jansi/jansi
44+
implementation 'org.fusesource.jansi:jansi:2.4.0'
45+
4346
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
4447
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
4548
}
@@ -72,6 +75,13 @@ if (hasProperty('buildScan')) {
7275
}
7376
}
7477

78+
task printCp {
79+
doLast {
80+
System.err.println sourceSets.main.runtimeClasspath.asPath
81+
}
82+
}
83+
7584
test {
7685
useJUnitPlatform()
86+
testLogging.showStandardStreams = true
7787
}
Lines changed: 111 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,36 @@
11
package org.hydev.mcpm.client.interaction;
22

3+
import org.fusesource.jansi.AnsiConsole;
4+
import org.hydev.mcpm.utils.ConsoleUtils;
5+
6+
import java.io.PrintStream;
7+
import java.util.*;
8+
9+
import static java.lang.String.format;
10+
import static org.fusesource.jansi.internal.CLibrary.STDOUT_FILENO;
11+
import static org.fusesource.jansi.internal.CLibrary.isatty;
12+
import static org.hydev.mcpm.utils.GeneralUtils.safeSleep;
13+
314
/**
4-
* TODO: Write a description for this class!
15+
* Terminal progress bar based on Xterm escape codes
516
*
617
* @author Azalea (https://github.com/hykilpikonna)
718
* @since 2022-09-27
819
*/
920
public class ProgressBar implements AutoCloseable
1021
{
22+
private final ConsoleUtils cu;
1123
private final ProgressBarTheme theme;
24+
private final PrintStream out;
25+
private int cols;
26+
27+
private final List<ProgressRow> activeBars;
28+
29+
private long lastUpdate;
30+
31+
private double frameDelay;
32+
33+
private final boolean istty;
1234

1335
/**
1436
* Create and initialize a progress bar
@@ -18,61 +40,118 @@ public class ProgressBar implements AutoCloseable
1840
public ProgressBar(ProgressBarTheme theme)
1941
{
2042
this.theme = theme;
21-
this.init();
22-
}
43+
this.out = System.out;
44+
this.cu = new ConsoleUtils(this.out);
45+
this.activeBars = new ArrayList<>();
46+
this.cols = AnsiConsole.getTerminalWidth();
2347

24-
/**
25-
* Initialize the progress bar (print the first line)
26-
*/
27-
public void init()
28-
{
29-
// TODO: Implement this
30-
throw new UnsupportedOperationException("TODO");
48+
// Default to 70-char width if the width can't be detected (like in a non-tty output)
49+
if (this.cols == 0) this.cols = 70;
50+
51+
// Last update time
52+
this.lastUpdate = System.nanoTime();
53+
54+
// Default frame delay is 0.01666 (60 fps)
55+
this.frameDelay = 1 / 60d;
56+
57+
// Check if output is a TTY. If not, change frame rate to 0.5 fps to avoid spamming a log.
58+
this.istty = isatty(STDOUT_FILENO) == 0;
59+
if (istty) this.frameDelay = 1 / 0.5;
3160
}
3261

3362
/**
3463
* Append a progress bar at the end
3564
*
36-
* @return Unique identifier of the progress bar
65+
* @param bar Row of the progress bar
66+
* @return bar for fluent access
3767
*/
38-
public String appendBar()
68+
public ProgressRow appendBar(ProgressRow bar)
3969
{
40-
// TODO: Implement this
41-
throw new UnsupportedOperationException("TODO");
70+
this.activeBars.add(bar);
71+
bar.setPb(this);
72+
73+
out.println();
74+
update();
75+
return bar;
4276
}
4377

44-
/**
45-
* Set progress for a bar
46-
*
47-
* @param id Unique identifier
48-
* @param progress Progress as a ratio in range 0-1
49-
*/
50-
public void setBar(String id, float progress)
78+
protected void update()
79+
{
80+
// Check time to limit for framerate (default 60fps)
81+
// Performance of the update heavily depends on the terminal's escape code handling
82+
// implementation, so frequent updates will degrade performance on a bad terminal
83+
var curTime = System.nanoTime();
84+
if ((curTime - lastUpdate) / 1e9d < frameDelay) return;
85+
lastUpdate = curTime;
86+
87+
forceUpdate();
88+
}
89+
90+
private void forceUpdate()
5191
{
52-
// TODO: Implement this
53-
throw new UnsupportedOperationException("TODO");
92+
// Roll back to the first line
93+
if (istty) cu.curUp(activeBars.size());
94+
activeBars.forEach(bar -> out.println(bar.toString(theme, cols)));
5495
}
5596

5697
/**
5798
* Finish a progress bar
5899
*
59-
* @param id Unique identifier of the progress bar
100+
* @param bar Progress bar
60101
*/
61-
public void finishBar(String id)
102+
public void finishBar(ProgressRow bar)
62103
{
63-
// TODO: Implement this
64-
throw new UnsupportedOperationException("TODO");
104+
if (!activeBars.contains(bar)) return;
105+
106+
forceUpdate();
107+
this.activeBars.remove(bar);
65108
}
66109

67110
/**
68111
* Finalize and close the progress bar (print the final line)
69-
*
70-
* @throws Exception e
71112
*/
72113
@Override
73-
public void close() throws Exception
114+
public void close()
115+
{
116+
}
117+
118+
public ProgressBar setFrameDelay(double frameDelay)
119+
{
120+
this.frameDelay = frameDelay;
121+
return this;
122+
}
123+
124+
/**
125+
* Set frame rate in the unit of frames per second
126+
*
127+
* @param fps FPS
128+
* @return Self for fluent access
129+
*/
130+
public ProgressBar setFps(int fps)
131+
{
132+
this.frameDelay = 1d / fps;
133+
return this;
134+
}
135+
136+
public List<ProgressRow> getActiveBars()
74137
{
75-
// TODO: Implement this
76-
throw new UnsupportedOperationException("TODO");
138+
return activeBars;
139+
}
140+
141+
public static void main(String[] args)
142+
{
143+
try (var b = new ProgressBar(ProgressBarTheme.ASCII_THEME))
144+
{
145+
var all = new ArrayList<ProgressRow>();
146+
for (int i = 0; i < 1300; i++)
147+
{
148+
if (i < 1000 && i % 100 == 0)
149+
all.add(b.appendBar(new ProgressRow(300).unit("MB").desc(format("File %s.tar.gz", all.size()))).descLen(30));
150+
all.forEach(a -> a.increase(1));
151+
safeSleep(3);
152+
}
153+
154+
System.out.println("Done");
155+
}
77156
}
78157
}

src/main/java/org/hydev/mcpm/client/interaction/ProgressBarTheme.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,16 @@
1212
* @since 2022-09-27
1313
*/
1414
public record ProgressBarTheme(
15-
String ipr,
1615
String done,
17-
int iprLen,
18-
int doneLen
16+
String ipr,
17+
String prefix,
18+
String suffix,
19+
int doneLen,
20+
int iprLen
1921
)
2022
{
21-
public static final ProgressBarTheme ASCII_THEME = new ProgressBarTheme("#", "-", 1, 1);
22-
public static final ProgressBarTheme CLASSIC_THEME = new ProgressBarTheme("█", ".", 1, 1);
23-
public static final ProgressBarTheme EMOJI_THEME = new ProgressBarTheme("✅", "🕑", 2, 2);
24-
public static final ProgressBarTheme FLOWER_THEME = new ProgressBarTheme("🌸", "🥀", 2, 2);
23+
public static final ProgressBarTheme ASCII_THEME = new ProgressBarTheme("#", "-", "[", "]", 1, 1);
24+
public static final ProgressBarTheme CLASSIC_THEME = new ProgressBarTheme("█", ".", "", "", 1, 1);
25+
public static final ProgressBarTheme EMOJI_THEME = new ProgressBarTheme("✅", "🕑", "", "", 2, 2);
26+
public static final ProgressBarTheme FLOWER_THEME = new ProgressBarTheme("🌸", "🥀", "", "", 2, 2);
2527
}

0 commit comments

Comments
 (0)