-
Notifications
You must be signed in to change notification settings - Fork 6
/
ProgressTracker.java
158 lines (137 loc) · 5.06 KB
/
ProgressTracker.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - info@scireum.de
*/
package sirius.biz.process;
import sirius.kernel.nls.NLS;
import javax.annotation.Nullable;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Optional;
/**
* Allows to track the progress of a long-running operation and calculates the estimated completion time.
*/
public class ProgressTracker {
private long total;
private LocalDateTime start;
private final ProcessContext processContext;
private long current = 0;
private boolean finished = false;
private String message = null;
/**
* Initializes a new instance of the progress tracker for the given process.
*
* @param processContext the process context to update the state message
*/
public ProgressTracker(ProcessContext processContext) {
this.processContext = processContext;
}
/**
* Starts the progress tracker with a total amount of units.
*
* @param total the total amount of units to track
*/
public void start(long total) {
if (total < 0) {
throw new IllegalArgumentException("Total must be >= 0");
}
this.start = LocalDateTime.now();
this.total = total;
}
/**
* Increments the processed unit by one and recalculates the progress.
*/
public void increment() {
increment(1);
}
/**
* Increments the processed unit by the amount given and recalculates the progress.
*
* @param amount the amount to increment the processed units by
*/
public void increment(long amount) {
assertTrackingStarted();
if (finished) {
throw new IllegalStateException("Cannot increment a finished progress tracker");
}
current = current + amount;
updateIntermediateState();
}
/**
* Finishes the progress tracking and updates the process with a final message containing the total elapsed time.
*/
public void finish() {
assertTrackingStarted();
finished = true;
updateFinalState();
}
/**
* Updates the plain-text additional message
*
* @param message the message to display
*/
public void updateMessage(@Nullable String message) {
assertTrackingStarted();
this.message = message;
updateState();
}
private void updateState() {
if (finished) {
updateFinalState();
} else {
updateIntermediateState();
}
}
private void updateIntermediateState() {
processContext.tryUpdateState(NLS.fmtr("ProgressTracker.state")
.set("progress", computeProgress())
.set("remaining",
computeRemainingTime().map(remaining -> NLS.convertDuration(remaining,
true,
false))
.orElse(null))
.set("completion", computeCompletionTime().map(NLS::toUserString).orElse(null))
.set("message", message)
.smartFormat());
}
private void updateFinalState() {
processContext.forceUpdateState(NLS.fmtr("ProgressTracker.total")
.set("total", NLS.convertDuration(computeElapsedDuration(), true, false))
.set("message", message)
.smartFormat());
}
private int computeProgress() {
if (total == 0) {
return 0;
}
return (int) ((double) current / total * 100);
}
private Optional<LocalDateTime> computeCompletionTime() {
if (total == 0 || current == 0) {
return Optional.empty();
}
long elapsedMillis = computeElapsedDuration().toMillis();
long totalMillis = (long) ((double) elapsedMillis * total / current);
return Optional.of(start.plus(totalMillis, ChronoUnit.MILLIS));
}
private Optional<Duration> computeRemainingTime() {
if (total == 0 || current == 0) {
return Optional.empty();
}
long elapsedMillis = computeElapsedDuration().toMillis();
long totalMillis = (long) ((double) elapsedMillis * total / current);
return Optional.of(Duration.ofMillis(totalMillis - elapsedMillis));
}
private Duration computeElapsedDuration() {
return Duration.between(start, LocalDateTime.now());
}
private void assertTrackingStarted() {
if (start == null) {
throw new IllegalStateException("The progress tracker has not been started yet!");
}
}
}