Skip to content

Commit 13f4108

Browse files
chore: introduce RemoteResource<T> interface (#712)
* chore: move DownloadException and DownloadUtils to `remote` package * feat: add `RemoteResource` interface * feat: make GameRelease a RemoteResource * chore: implement GameManager#download with DownloadUtils * inline use of (deprecated) GameManager::getFileNameFor * chore: remove unused import --------- Co-authored-by: jdrueckert <[email protected]>
1 parent 8237b1e commit 13f4108

File tree

6 files changed

+104
-55
lines changed

6 files changed

+104
-55
lines changed

src/main/java/org/terasology/launcher/game/GameManager.java

Lines changed: 13 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,17 @@
1010
import org.slf4j.LoggerFactory;
1111
import org.terasology.launcher.model.GameIdentifier;
1212
import org.terasology.launcher.model.GameRelease;
13+
import org.terasology.launcher.remote.DownloadException;
14+
import org.terasology.launcher.remote.DownloadUtils;
15+
import org.terasology.launcher.remote.RemoteResource;
1316
import org.terasology.launcher.tasks.ProgressListener;
14-
import org.terasology.launcher.util.DownloadException;
15-
import org.terasology.launcher.util.DownloadUtils;
1617
import org.terasology.launcher.util.FileUtils;
1718

1819
import java.io.File;
1920
import java.io.FileNotFoundException;
2021
import java.io.IOException;
21-
import java.net.URL;
2222
import java.nio.file.Files;
2323
import java.nio.file.Path;
24-
import java.nio.file.StandardCopyOption;
2524
import java.util.Comparator;
2625
import java.util.Objects;
2726
import java.util.Set;
@@ -52,25 +51,14 @@ public GameManager(Path cacheDirectory, Path installDirectory) {
5251
scanInstallationDir();
5352
}
5453

55-
/**
56-
* Derive the file name for the downloaded ZIP package from the game release.
57-
*/
58-
private String getFileNameFor(GameRelease release) {
59-
GameIdentifier id = release.getId();
60-
String profileString = id.getProfile().toString().toLowerCase();
61-
String versionString = id.getDisplayVersion();
62-
String buildString = id.getBuild().toString().toLowerCase();
63-
return "terasology-" + profileString + "-" + versionString + "-" + buildString + ".zip";
64-
}
65-
6654
/**
6755
* Installs the given release to the local file system.
6856
*
6957
* @param release the game release to be installed
7058
* @param listener the object which is to be informed about task progress
7159
*/
7260
public void install(GameRelease release, ProgressListener listener) throws IOException, DownloadException, InterruptedException {
73-
final Path cachedZip = cacheDirectory.resolve(getFileNameFor(release));
61+
final Path cachedZip = cacheDirectory.resolve(release.getFilename());
7462

7563
// TODO: Properly validate cache and handle exceptions
7664
if (Files.notExists(cachedZip)) {
@@ -85,30 +73,18 @@ public void install(GameRelease release, ProgressListener listener) throws IOExc
8573
}
8674
}
8775

76+
/**
77+
* @deprecated Use {@link DownloadUtils#download(RemoteResource, Path, ProgressListener)} instead.
78+
*/
79+
@Deprecated
8880
private void download(GameRelease release, Path targetLocation, ProgressListener listener)
8981
throws DownloadException, IOException, InterruptedException {
90-
final URL downloadUrl = release.getUrl();
91-
92-
final long contentLength = DownloadUtils.getContentLength(downloadUrl);
93-
final long availableSpace = targetLocation.getParent().toFile().getUsableSpace();
94-
95-
if (availableSpace >= contentLength) {
96-
final Path cacheZipPart = targetLocation.resolveSibling(targetLocation.getFileName().toString() + ".part");
97-
Files.deleteIfExists(cacheZipPart);
98-
try {
99-
DownloadUtils.downloadToFile(downloadUrl, cacheZipPart, listener).get();
100-
} catch (ExecutionException e) {
101-
throw new DownloadException("Exception while downloading " + downloadUrl, e.getCause());
102-
}
103-
104-
if (!listener.isCancelled()) {
105-
Files.move(cacheZipPart, targetLocation, StandardCopyOption.ATOMIC_MOVE);
106-
}
107-
} else {
108-
throw new DownloadException("Insufficient space for downloading package");
82+
DownloadUtils downloader = new DownloadUtils();
83+
try {
84+
downloader.download(release, targetLocation, listener).get();
85+
} catch (ExecutionException e) {
86+
throw new DownloadException("Download failed.", e.getCause());
10987
}
110-
111-
logger.info("Finished downloading package: {}", release.getId());
11288
}
11389

11490
/**

src/main/java/org/terasology/launcher/model/GameRelease.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package org.terasology.launcher.model;
55

6+
import org.terasology.launcher.remote.RemoteResource;
7+
68
import java.net.URL;
79
import java.util.Date;
810
import java.util.Objects;
@@ -17,7 +19,7 @@
1719
* <li>TODO: define what the <b>artifact</b> is, and what requirements/restrictions there are</li>
1820
* </ul>
1921
*/
20-
public class GameRelease {
22+
public class GameRelease implements RemoteResource<GameIdentifier> {
2123
final GameIdentifier id;
2224
final ReleaseMetadata releaseMetadata;
2325
final URL url;
@@ -36,6 +38,19 @@ public URL getUrl() {
3638
return url;
3739
}
3840

41+
@Override
42+
public String getFilename() {
43+
String profileString = id.getProfile().toString().toLowerCase();
44+
String versionString = id.getDisplayVersion();
45+
String buildString = id.getBuild().toString().toLowerCase();
46+
return "terasology-" + profileString + "-" + versionString + "-" + buildString + ".zip";
47+
}
48+
49+
@Override
50+
public GameIdentifier getInfo() {
51+
return id;
52+
}
53+
3954
/**
4055
* The changelog associated with the game release
4156
*/

src/main/java/org/terasology/launcher/util/DownloadException.java renamed to src/main/java/org/terasology/launcher/remote/DownloadException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2023 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package org.terasology.launcher.util;
4+
package org.terasology.launcher.remote;
55

66

77
public final class DownloadException extends RuntimeException {

src/main/java/org/terasology/launcher/util/DownloadUtils.java renamed to src/main/java/org/terasology/launcher/remote/DownloadUtils.java

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,81 @@
1-
// Copyright 2021 The Terasology Foundation
1+
// Copyright 2023 The Terasology Foundation
22
// SPDX-License-Identifier: Apache-2.0
33

4-
package org.terasology.launcher.util;
4+
package org.terasology.launcher.remote;
55

66
import org.slf4j.Logger;
77
import org.slf4j.LoggerFactory;
88
import org.terasology.launcher.tasks.ProgressListener;
99

10+
import javax.net.ssl.HttpsURLConnection;
1011
import java.io.BufferedInputStream;
1112
import java.io.BufferedOutputStream;
1213
import java.io.IOException;
1314
import java.io.InputStream;
14-
import java.net.HttpURLConnection;
1515
import java.net.URISyntaxException;
1616
import java.net.URL;
1717
import java.net.http.HttpClient;
1818
import java.net.http.HttpRequest;
1919
import java.net.http.HttpResponse;
2020
import java.nio.file.Files;
2121
import java.nio.file.Path;
22+
import java.nio.file.StandardCopyOption;
2223
import java.time.Duration;
2324
import java.util.concurrent.CompletableFuture;
25+
import java.util.concurrent.ExecutionException;
2426

2527
public final class DownloadUtils {
2628

2729
private static final Logger logger = LoggerFactory.getLogger(DownloadUtils.class);
2830

29-
private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(30);
30-
private static final Duration READ_TIMEOUT = Duration.ofMinutes(5);
31+
private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(30);
32+
private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(5);
3133

32-
private DownloadUtils() {
34+
private final Duration connectTimeout; //TODO: use instead of default
35+
private final Duration readTimeout; //TODO: use instead of default
36+
37+
public DownloadUtils() {
38+
this(DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT);
39+
}
40+
41+
public DownloadUtils(Duration connectTimeout, Duration readTimeout) {
42+
this.connectTimeout = connectTimeout;
43+
this.readTimeout = readTimeout;
44+
}
45+
46+
public <T> CompletableFuture<Path> download(RemoteResource<T> resource, Path path, ProgressListener listener)
47+
throws DownloadException, IOException, InterruptedException {
48+
final URL downloadUrl = resource.getUrl();
49+
50+
final long contentLength = DownloadUtils.getContentLength(downloadUrl);
51+
final long availableSpace = path.getParent().toFile().getUsableSpace();
52+
53+
if (availableSpace >= contentLength) {
54+
final Path cacheZipPart = path.resolveSibling(path.getFileName().toString() + ".part");
55+
Files.deleteIfExists(cacheZipPart);
56+
try {
57+
DownloadUtils.downloadToFile(downloadUrl, cacheZipPart, listener).get();
58+
} catch (ExecutionException e) {
59+
throw new DownloadException("Exception while downloading " + downloadUrl, e.getCause());
60+
}
61+
62+
if (!listener.isCancelled()) {
63+
Files.move(cacheZipPart, path, StandardCopyOption.ATOMIC_MOVE);
64+
}
65+
} else {
66+
throw new DownloadException("Insufficient space for downloading package");
67+
}
68+
69+
logger.info("Finished downloading package: {}", resource.getInfo());
70+
71+
72+
return CompletableFuture.supplyAsync(() -> path);
3373
}
3474

75+
/**
76+
* @deprecated Use {@link #download(RemoteResource, Path, ProgressListener)} instead;
77+
*/
78+
@Deprecated
3579
public static CompletableFuture<Void> downloadToFile(URL downloadURL, Path file, ProgressListener listener) throws DownloadException {
3680
listener.update(0);
3781

@@ -62,30 +106,27 @@ public static CompletableFuture<Void> downloadToFile(URL downloadURL, Path file,
62106
});
63107
}
64108

109+
@Deprecated
65110
public static long getContentLength(URL downloadURL) throws DownloadException {
66-
HttpURLConnection connection = null;
111+
HttpsURLConnection connection = null;
67112
try {
68-
connection = (HttpURLConnection) downloadURL.openConnection();
113+
connection = (HttpsURLConnection) downloadURL.openConnection();
69114
connection.setRequestMethod("HEAD");
70115
return connection.getContentLengthLong();
71116
} catch (IOException e) {
72117
throw new DownloadException("Could not send HEAD request to HTTP-URL! URL=" + downloadURL, e);
73-
} finally {
74-
if (connection != null) {
75-
connection.disconnect();
76-
}
77118
}
78119
}
79120

80121
private static CompletableFuture<HttpResponse<InputStream>> getConnectedDownloadConnection(URL downloadURL) throws DownloadException {
81122
var client = HttpClient.newBuilder()
82123
.followRedirects(HttpClient.Redirect.NORMAL)
83-
.connectTimeout(CONNECT_TIMEOUT)
124+
.connectTimeout(DEFAULT_CONNECT_TIMEOUT)
84125
.build();
85126

86127
HttpRequest request;
87128
try {
88-
request = HttpRequest.newBuilder(downloadURL.toURI()).timeout(READ_TIMEOUT).build();
129+
request = HttpRequest.newBuilder(downloadURL.toURI()).timeout(DEFAULT_READ_TIMEOUT).build();
89130
} catch (URISyntaxException e) {
90131
throw new DownloadException("Error in URL: " + downloadURL, e);
91132
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2023 The Terasology Foundation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package org.terasology.launcher.remote;
5+
6+
import java.net.URL;
7+
8+
public interface RemoteResource<T> {
9+
10+
URL getUrl();
11+
12+
String getFilename();
13+
14+
T getInfo();
15+
16+
//TODO: String getChecksum();
17+
}

src/main/java/org/terasology/launcher/tasks/DownloadTask.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import org.slf4j.LoggerFactory;
99
import org.terasology.launcher.game.GameManager;
1010
import org.terasology.launcher.model.GameRelease;
11-
import org.terasology.launcher.util.DownloadException;
11+
import org.terasology.launcher.remote.DownloadException;
1212

1313
import java.io.IOException;
1414

0 commit comments

Comments
 (0)