Skip to content

Commit c55b9ea

Browse files
Add Downloader abstract class
1 parent 7bf1905 commit c55b9ea

3 files changed

Lines changed: 137 additions & 23 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.wildermods.thrixlvault;
2+
3+
import java.io.IOException;
4+
import java.util.Collection;
5+
import java.util.Set;
6+
7+
import com.google.common.collect.ImmutableSet;
8+
import com.wildermods.thrixlvault.steam.IDownload;
9+
import com.wildermods.thrixlvault.steam.IDownloadable;
10+
11+
/**
12+
* Base class representing a generic downloader for a set of {@link IDownloadable} items.
13+
*
14+
* <p>This class provides a framework for performing downloads and tracking their progress.
15+
* Subclasses must implement the actual download logic in {@link #runImpl()} and optionally
16+
* provide support for accessing downloads while they are in progress through
17+
* {@link #getDownloadsInProgress()}.</p>
18+
*
19+
* <p>The class distinguishes between downloads that are still in progress and those
20+
* that are fully completed:</p>
21+
* <ul>
22+
* <li>{@link #getDownloads()} returns the set of downloads either in progress or completed.
23+
* By default, it delegates to {@link #getDownloadsInProgress()} if the downloads are not
24+
* finished yet, and to {@link #getFinishedDownloads()} once complete.</li>
25+
* <li>{@link #getFinishedDownloads()} returns the completed downloads only and throws an
26+
* {@link IllegalStateException} if called before completion.</li>
27+
* </ul>
28+
*
29+
* <p>The {@link #run()} method executes the download synchronously and blocks until all downloads
30+
* are finished. After completion, the results are cached as an immutable set. If asynchronous or
31+
* incremental download reporting is desired, subclasses may override {@link #getDownloadsInProgress()}.</p>
32+
*
33+
* @param <Downloadable> The type of items to download, must implement {@link IDownloadable}.
34+
* @param <Download> The type representing a completed download, must implement {@link IDownload}.
35+
*/
36+
public abstract class Downloader<Downloadable extends IDownloadable, Download extends IDownload> {
37+
38+
private final Object runLock = new Object();
39+
40+
protected final Set<Downloadable> downloadables;
41+
private volatile Set<Download> finishedDownloads;
42+
private volatile boolean finished = false;
43+
44+
public Downloader(Collection<Downloadable> downloadables) {
45+
this.downloadables = ImmutableSet.copyOf(downloadables);
46+
}
47+
48+
/**
49+
* Runs the downloader synchronously and blocks until all downloads are complete.
50+
*
51+
* @return an immutable set of completed downloads.
52+
* @throws IOException
53+
*/
54+
public final Set<Download> run() throws IOException {
55+
synchronized(runLock) {
56+
finishedDownloads = ImmutableSet.copyOf(runImpl());
57+
finished = true;
58+
return finishedDownloads;
59+
}
60+
}
61+
62+
protected abstract Set<Download> runImpl() throws IOException;
63+
64+
public final boolean isFinished() {
65+
return finished;
66+
}
67+
68+
public final Set<Download> getFinishedDownloads() {
69+
if(finished) {
70+
return finishedDownloads;
71+
}
72+
throw new IllegalStateException("Download in progress");
73+
}
74+
75+
public final Set<Download> getDownloads() throws UnsupportedOperationException {
76+
if(finished) {
77+
return getFinishedDownloads();
78+
}
79+
return getDownloadsInProgress();
80+
}
81+
82+
protected abstract ImmutableSet<Download> getDownloadsInProgress() throws UnsupportedOperationException;
83+
84+
}

src/main/java/com/wildermods/thrixlvault/MassDownloadWeaver.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,32 +7,34 @@
77
import java.util.LinkedHashSet;
88
import java.util.Set;
99

10+
import com.google.common.collect.ImmutableSet;
1011
import com.wildermods.masshash.exception.IntegrityException;
1112
import com.wildermods.thrixlvault.steam.CompletedDownload;
1213
import com.wildermods.thrixlvault.steam.FailedDownload;
1314
import com.wildermods.thrixlvault.steam.ISteamDownload;
1415
import com.wildermods.thrixlvault.steam.ISteamDownloadable;
1516
import com.wildermods.thrixlvault.utils.FileUtil;
1617

17-
public class MassDownloadWeaver {
18+
public class MassDownloadWeaver extends Downloader<ISteamDownloadable, ISteamDownload>{
1819

1920
final String username;
2021
final int totalDownloads;
21-
final LinkedHashSet<ISteamDownloadable> downloadables;
2222
final HashMap<ISteamDownloadable, Integer> failedDownloads = new HashMap<>();
23-
final HashSet<ISteamDownload> finishedDownloads = new HashSet<ISteamDownload>();
2423

25-
public MassDownloadWeaver(String username, Collection<ISteamDownloadable> downloadable) throws IOException, InterruptedException {
24+
public MassDownloadWeaver(String username, Collection<ISteamDownloadable> downloadables) throws IOException, InterruptedException {
25+
super(downloadables);
2626
this.username = username;
27-
totalDownloads = downloadable.size();
28-
downloadables = new LinkedHashSet<>(downloadable);
27+
totalDownloads = downloadables.size();
2928
}
3029

31-
public void run() throws IOException, InterruptedException {
30+
public Set<ISteamDownload> runImpl() throws IOException {
31+
final HashSet<ISteamDownloadable> downloadables = new LinkedHashSet<>(this.downloadables);
32+
final HashSet<ISteamDownload> finishedDownloads = new HashSet<ISteamDownload>();
3233
while(!downloadables.isEmpty()) {
3334

3435
SteamDownloader downloader = new SteamDownloader(username, downloadables);
35-
Set<ISteamDownload> downloads = downloader.run((download) -> {
36+
37+
downloader.setConsumer((download) -> {
3638
if(download instanceof CompletedDownload) {
3739
try {
3840
Weaver weaver = new Weaver(Vault.DEFAULT, download);
@@ -46,6 +48,8 @@ public void run() throws IOException, InterruptedException {
4648
throw new RuntimeException(e);
4749
}
4850
});
51+
52+
Set<ISteamDownload> downloads = downloader.run();
4953
for(ISteamDownload download : downloads) {
5054
if(download instanceof FailedDownload) {
5155
final int attempt;
@@ -94,6 +98,12 @@ else if(download instanceof FailedDownload) {
9498
System.out.println("Remaining downloads:" + remaining + "/" + totalDownloads);
9599
System.out.println("Other status downloads: " + other + "/" + totalDownloads);
96100
System.out.println("Downloads unaccounted for: " + (totalDownloads - (failed + success + other + remaining)));
101+
return finishedDownloads;
102+
}
103+
104+
@Override
105+
protected ImmutableSet<ISteamDownload> getDownloadsInProgress() throws UnsupportedOperationException {
106+
throw new UnsupportedOperationException();
97107
}
98108

99109
}

src/main/java/com/wildermods/thrixlvault/SteamDownloader.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import javax.security.auth.login.CredentialException;
2222
import javax.security.sasl.AuthenticationException;
2323

24+
import com.google.common.collect.ImmutableSet;
2425
import com.wildermods.thrixlvault.steam.CompletedDownload;
2526
import com.wildermods.thrixlvault.steam.FailedDownload;
2627
import com.wildermods.thrixlvault.steam.IManifest;
@@ -30,50 +31,61 @@
3031

3132
import static com.wildermods.thrixlvault.SteamDownloader.SteamState.*;
3233

33-
public class SteamDownloader {
34+
public class SteamDownloader extends Downloader<ISteamDownloadable, ISteamDownload> {
3435

3536
private static final Object GLOBAL_RUN_LOCK = new Object(); // So we only have one instance of steamcmd operating at a time.
3637

3738
public static final Path DEFAULT_APP_INSTALL_DIR = Path.of(System.getProperty("user.home")).resolve("thrixlvault").resolve("current_download");
3839
private final Path installDir;
3940
private final String username;
40-
private final Collection<ISteamDownloadable> downloadables;
4141
private final HashMap<IManifest, ISteamDownload> processedDownloads = new HashMap<IManifest, ISteamDownload>();
4242

4343
private volatile Process process;
4444
private volatile SteamState prevState = SteamState.UNINITIALIZED;
4545
private volatile SteamState steamState = SteamState.UNINITIALIZED;
4646
private volatile boolean userInput = false;
4747
private volatile Instant lastResponse = Instant.now();
48+
private volatile Duration hangTimeout = Duration.ofSeconds(30);
49+
private volatile Duration downloadTimeout = Duration.ofMinutes(7);
50+
private volatile Consumer<ISteamDownload> onManifestDownload = (d) -> {};
4851

4952
private final Object interruptLock = new Object();
53+
private final Object stateLock = new Object();
5054
private volatile boolean interrupt = false;;
5155

5256
private volatile ISteamDownloadable currentDownload;
5357

5458
private static final Pattern ERROR_REGEX = Pattern.compile("ERROR \\((?<error>.*)\\)\\n");
5559

5660
public SteamDownloader(String username, Collection<ISteamDownloadable> downloads) {
61+
super(downloads);
5762
this.username = username;
58-
this.downloadables = downloads;
5963
this.installDir = DEFAULT_APP_INSTALL_DIR;
6064
}
6165

6266
public SteamDownloader(String username, Collection<ISteamDownloadable> downloads, Path installDir) {
67+
super(downloads);
6368
this.username = username;
64-
this.downloadables = downloads;
6569
this.installDir = installDir;
6670
}
6771

68-
public Set<ISteamDownload> run() throws Throwable {
69-
return run((c) -> {});
72+
public SteamDownloader setHangTimeout(Duration hangTimeout) {
73+
this.hangTimeout = hangTimeout;
74+
return this;
7075
}
7176

72-
public Set<ISteamDownload> run(Consumer<ISteamDownload> onManifestDownload) throws IOException, InterruptedException {
73-
return run(onManifestDownload, Duration.ofSeconds(30), Duration.ofMinutes(7));
77+
public SteamDownloader setDownloadTimeout(Duration downloadTimeout) {
78+
this.downloadTimeout = downloadTimeout;
79+
return this;
7480
}
75-
76-
public Set<ISteamDownload> run(Consumer<ISteamDownload> onManifestDownload, Duration hangTimeout, Duration downloadTimeout) throws IOException, InterruptedException {
81+
82+
public SteamDownloader setConsumer(Consumer<ISteamDownload> onManifestDownload) {
83+
this.onManifestDownload = onManifestDownload;
84+
return this;
85+
}
86+
87+
@Override
88+
public Set<ISteamDownload> runImpl() throws IOException {
7789
synchronized (GLOBAL_RUN_LOCK) {
7890
System.out.println("[DOWNLOADER] Acquired global run lock");
7991
return this.runInternal(onManifestDownload, hangTimeout, downloadTimeout);
@@ -328,6 +340,11 @@ else if (state == DOWNLOADING) {
328340
return Set.copyOf(processedDownloads.values());
329341
}
330342

343+
@Override
344+
protected ImmutableSet<ISteamDownload> getDownloadsInProgress() throws UnsupportedOperationException {
345+
throw new UnsupportedOperationException();
346+
}
347+
331348
private void putDownload(Consumer<ISteamDownload> onManifestDownload, IManifest manifest, ISteamDownload download) {
332349
this.processedDownloads.put(manifest, download);
333350
onManifestDownload.accept(download);
@@ -382,14 +399,17 @@ public SteamState getState() {
382399
return steamState;
383400
}
384401

385-
private synchronized SteamState setState(SteamState newState) {
386-
prevState = getState();
387-
steamState = newState;
388-
System.out.println("\n[DOWNLOADER/STATE]: Steam state changed from " + prevState + " to " + newState);
389-
return prevState;
402+
private SteamState setState(SteamState newState) {
403+
synchronized(stateLock) {
404+
prevState = getState();
405+
steamState = newState;
406+
System.out.println("\n[DOWNLOADER/STATE]: Steam state changed from " + prevState + " to " + newState);
407+
return prevState;
408+
}
390409
}
391410

392411
public enum SteamState {
393412
UNINITIALIZED, INITIALIZE, SETUP_DIR, LOGGING_IN, DOWNLOADING, FINISHED, FAILURE
394413
}
414+
395415
}

0 commit comments

Comments
 (0)