diff --git a/src/main/java/org/terasology/launcher/game/GameManager.java b/src/main/java/org/terasology/launcher/game/GameManager.java
index 451cd128..d6b236d0 100644
--- a/src/main/java/org/terasology/launcher/game/GameManager.java
+++ b/src/main/java/org/terasology/launcher/game/GameManager.java
@@ -10,18 +10,17 @@
import org.slf4j.LoggerFactory;
import org.terasology.launcher.model.GameIdentifier;
import org.terasology.launcher.model.GameRelease;
+import org.terasology.launcher.remote.DownloadException;
+import org.terasology.launcher.remote.DownloadUtils;
+import org.terasology.launcher.remote.RemoteResource;
import org.terasology.launcher.tasks.ProgressListener;
-import org.terasology.launcher.util.DownloadException;
-import org.terasology.launcher.util.DownloadUtils;
import org.terasology.launcher.util.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
-import java.nio.file.StandardCopyOption;
import java.util.Comparator;
import java.util.Objects;
import java.util.Set;
@@ -52,17 +51,6 @@ public GameManager(Path cacheDirectory, Path installDirectory) {
scanInstallationDir();
}
- /**
- * Derive the file name for the downloaded ZIP package from the game release.
- */
- private String getFileNameFor(GameRelease release) {
- GameIdentifier id = release.getId();
- String profileString = id.getProfile().toString().toLowerCase();
- String versionString = id.getDisplayVersion();
- String buildString = id.getBuild().toString().toLowerCase();
- return "terasology-" + profileString + "-" + versionString + "-" + buildString + ".zip";
- }
-
/**
* Installs the given release to the local file system.
*
@@ -70,7 +58,7 @@ private String getFileNameFor(GameRelease release) {
* @param listener the object which is to be informed about task progress
*/
public void install(GameRelease release, ProgressListener listener) throws IOException, DownloadException, InterruptedException {
- final Path cachedZip = cacheDirectory.resolve(getFileNameFor(release));
+ final Path cachedZip = cacheDirectory.resolve(release.getFilename());
// TODO: Properly validate cache and handle exceptions
if (Files.notExists(cachedZip)) {
@@ -85,30 +73,18 @@ public void install(GameRelease release, ProgressListener listener) throws IOExc
}
}
+ /**
+ * @deprecated Use {@link DownloadUtils#download(RemoteResource, Path, ProgressListener)} instead.
+ */
+ @Deprecated
private void download(GameRelease release, Path targetLocation, ProgressListener listener)
throws DownloadException, IOException, InterruptedException {
- final URL downloadUrl = release.getUrl();
-
- final long contentLength = DownloadUtils.getContentLength(downloadUrl);
- final long availableSpace = targetLocation.getParent().toFile().getUsableSpace();
-
- if (availableSpace >= contentLength) {
- final Path cacheZipPart = targetLocation.resolveSibling(targetLocation.getFileName().toString() + ".part");
- Files.deleteIfExists(cacheZipPart);
- try {
- DownloadUtils.downloadToFile(downloadUrl, cacheZipPart, listener).get();
- } catch (ExecutionException e) {
- throw new DownloadException("Exception while downloading " + downloadUrl, e.getCause());
- }
-
- if (!listener.isCancelled()) {
- Files.move(cacheZipPart, targetLocation, StandardCopyOption.ATOMIC_MOVE);
- }
- } else {
- throw new DownloadException("Insufficient space for downloading package");
+ DownloadUtils downloader = new DownloadUtils();
+ try {
+ downloader.download(release, targetLocation, listener).get();
+ } catch (ExecutionException e) {
+ throw new DownloadException("Download failed.", e.getCause());
}
-
- logger.info("Finished downloading package: {}", release.getId());
}
/**
diff --git a/src/main/java/org/terasology/launcher/model/GameRelease.java b/src/main/java/org/terasology/launcher/model/GameRelease.java
index 9a66e7d4..deb4e02c 100644
--- a/src/main/java/org/terasology/launcher/model/GameRelease.java
+++ b/src/main/java/org/terasology/launcher/model/GameRelease.java
@@ -3,6 +3,8 @@
package org.terasology.launcher.model;
+import org.terasology.launcher.remote.RemoteResource;
+
import java.net.URL;
import java.util.Date;
import java.util.Objects;
@@ -17,7 +19,7 @@
*
TODO: define what the artifact is, and what requirements/restrictions there are
*
*/
-public class GameRelease {
+public class GameRelease implements RemoteResource {
final GameIdentifier id;
final ReleaseMetadata releaseMetadata;
final URL url;
@@ -36,6 +38,19 @@ public URL getUrl() {
return url;
}
+ @Override
+ public String getFilename() {
+ String profileString = id.getProfile().toString().toLowerCase();
+ String versionString = id.getDisplayVersion();
+ String buildString = id.getBuild().toString().toLowerCase();
+ return "terasology-" + profileString + "-" + versionString + "-" + buildString + ".zip";
+ }
+
+ @Override
+ public GameIdentifier getInfo() {
+ return id;
+ }
+
/**
* The changelog associated with the game release
*/
diff --git a/src/main/java/org/terasology/launcher/util/DownloadException.java b/src/main/java/org/terasology/launcher/remote/DownloadException.java
similarity index 85%
rename from src/main/java/org/terasology/launcher/util/DownloadException.java
rename to src/main/java/org/terasology/launcher/remote/DownloadException.java
index 43961be1..77eb6803 100644
--- a/src/main/java/org/terasology/launcher/util/DownloadException.java
+++ b/src/main/java/org/terasology/launcher/remote/DownloadException.java
@@ -1,7 +1,7 @@
-// Copyright 2021 The Terasology Foundation
+// Copyright 2023 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
-package org.terasology.launcher.util;
+package org.terasology.launcher.remote;
public final class DownloadException extends RuntimeException {
diff --git a/src/main/java/org/terasology/launcher/util/DownloadUtils.java b/src/main/java/org/terasology/launcher/remote/DownloadUtils.java
similarity index 64%
rename from src/main/java/org/terasology/launcher/util/DownloadUtils.java
rename to src/main/java/org/terasology/launcher/remote/DownloadUtils.java
index 0d711ebc..9beb28a0 100644
--- a/src/main/java/org/terasology/launcher/util/DownloadUtils.java
+++ b/src/main/java/org/terasology/launcher/remote/DownloadUtils.java
@@ -1,17 +1,17 @@
-// Copyright 2021 The Terasology Foundation
+// Copyright 2023 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0
-package org.terasology.launcher.util;
+package org.terasology.launcher.remote;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.launcher.tasks.ProgressListener;
+import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
@@ -19,19 +19,63 @@
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.time.Duration;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
public final class DownloadUtils {
private static final Logger logger = LoggerFactory.getLogger(DownloadUtils.class);
- private static final Duration CONNECT_TIMEOUT = Duration.ofSeconds(30);
- private static final Duration READ_TIMEOUT = Duration.ofMinutes(5);
+ private static final Duration DEFAULT_CONNECT_TIMEOUT = Duration.ofSeconds(30);
+ private static final Duration DEFAULT_READ_TIMEOUT = Duration.ofMinutes(5);
- private DownloadUtils() {
+ private final Duration connectTimeout; //TODO: use instead of default
+ private final Duration readTimeout; //TODO: use instead of default
+
+ public DownloadUtils() {
+ this(DEFAULT_CONNECT_TIMEOUT, DEFAULT_READ_TIMEOUT);
+ }
+
+ public DownloadUtils(Duration connectTimeout, Duration readTimeout) {
+ this.connectTimeout = connectTimeout;
+ this.readTimeout = readTimeout;
+ }
+
+ public CompletableFuture download(RemoteResource resource, Path path, ProgressListener listener)
+ throws DownloadException, IOException, InterruptedException {
+ final URL downloadUrl = resource.getUrl();
+
+ final long contentLength = DownloadUtils.getContentLength(downloadUrl);
+ final long availableSpace = path.getParent().toFile().getUsableSpace();
+
+ if (availableSpace >= contentLength) {
+ final Path cacheZipPart = path.resolveSibling(path.getFileName().toString() + ".part");
+ Files.deleteIfExists(cacheZipPart);
+ try {
+ DownloadUtils.downloadToFile(downloadUrl, cacheZipPart, listener).get();
+ } catch (ExecutionException e) {
+ throw new DownloadException("Exception while downloading " + downloadUrl, e.getCause());
+ }
+
+ if (!listener.isCancelled()) {
+ Files.move(cacheZipPart, path, StandardCopyOption.ATOMIC_MOVE);
+ }
+ } else {
+ throw new DownloadException("Insufficient space for downloading package");
+ }
+
+ logger.info("Finished downloading package: {}", resource.getInfo());
+
+
+ return CompletableFuture.supplyAsync(() -> path);
}
+ /**
+ * @deprecated Use {@link #download(RemoteResource, Path, ProgressListener)} instead;
+ */
+ @Deprecated
public static CompletableFuture downloadToFile(URL downloadURL, Path file, ProgressListener listener) throws DownloadException {
listener.update(0);
@@ -62,30 +106,27 @@ public static CompletableFuture downloadToFile(URL downloadURL, Path file,
});
}
+ @Deprecated
public static long getContentLength(URL downloadURL) throws DownloadException {
- HttpURLConnection connection = null;
+ HttpsURLConnection connection = null;
try {
- connection = (HttpURLConnection) downloadURL.openConnection();
+ connection = (HttpsURLConnection) downloadURL.openConnection();
connection.setRequestMethod("HEAD");
return connection.getContentLengthLong();
} catch (IOException e) {
throw new DownloadException("Could not send HEAD request to HTTP-URL! URL=" + downloadURL, e);
- } finally {
- if (connection != null) {
- connection.disconnect();
- }
}
}
private static CompletableFuture> getConnectedDownloadConnection(URL downloadURL) throws DownloadException {
var client = HttpClient.newBuilder()
.followRedirects(HttpClient.Redirect.NORMAL)
- .connectTimeout(CONNECT_TIMEOUT)
+ .connectTimeout(DEFAULT_CONNECT_TIMEOUT)
.build();
HttpRequest request;
try {
- request = HttpRequest.newBuilder(downloadURL.toURI()).timeout(READ_TIMEOUT).build();
+ request = HttpRequest.newBuilder(downloadURL.toURI()).timeout(DEFAULT_READ_TIMEOUT).build();
} catch (URISyntaxException e) {
throw new DownloadException("Error in URL: " + downloadURL, e);
}
diff --git a/src/main/java/org/terasology/launcher/remote/RemoteResource.java b/src/main/java/org/terasology/launcher/remote/RemoteResource.java
new file mode 100644
index 00000000..74f6d9c5
--- /dev/null
+++ b/src/main/java/org/terasology/launcher/remote/RemoteResource.java
@@ -0,0 +1,17 @@
+// Copyright 2023 The Terasology Foundation
+// SPDX-License-Identifier: Apache-2.0
+
+package org.terasology.launcher.remote;
+
+import java.net.URL;
+
+public interface RemoteResource {
+
+ URL getUrl();
+
+ String getFilename();
+
+ T getInfo();
+
+ //TODO: String getChecksum();
+}
diff --git a/src/main/java/org/terasology/launcher/tasks/DownloadTask.java b/src/main/java/org/terasology/launcher/tasks/DownloadTask.java
index 8983e161..b01dc475 100644
--- a/src/main/java/org/terasology/launcher/tasks/DownloadTask.java
+++ b/src/main/java/org/terasology/launcher/tasks/DownloadTask.java
@@ -8,7 +8,7 @@
import org.slf4j.LoggerFactory;
import org.terasology.launcher.game.GameManager;
import org.terasology.launcher.model.GameRelease;
-import org.terasology.launcher.util.DownloadException;
+import org.terasology.launcher.remote.DownloadException;
import java.io.IOException;