diff --git a/pom.xml b/pom.xml
index cba4e3d..5138603 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,21 @@
https://git.724.rocks/api/packages/Minecon724/maven
+
+
+
+ com.google.code.gson
+ gson
+ 2.11.0
+ true
+
+
+ junit
+ junit
+ 4.13.2
+ test
+
+
diff --git a/src/main/java/eu/m724/jarupdater/Updater.java b/src/main/java/eu/m724/jarupdater/Updater.java
index 6dabea7..be07850 100644
--- a/src/main/java/eu/m724/jarupdater/Updater.java
+++ b/src/main/java/eu/m724/jarupdater/Updater.java
@@ -1,8 +1,11 @@
package eu.m724.jarupdater;
import java.io.File;
+import java.io.IOException;
import java.nio.file.NoSuchFileException;
+import java.security.SignatureException;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
import eu.m724.jarupdater.download.Downloader;
import eu.m724.jarupdater.environment.Environment;
@@ -26,6 +29,10 @@ public class Updater {
return environment;
}
+ /**
+ * get the latest available version
+ * @return a future which can throw ioexpception
+ */
public CompletableFuture getLatestVersion() {
CompletableFuture currentVersionFuture = metadataProvider.getCurrentVersionMetadata();
CompletableFuture latestVersionFuture = metadataProvider.getLatestVersionMetadata();
@@ -34,14 +41,25 @@ public class Updater {
Version currentVersion = currentVersionFuture.join();
Version latestVersion = latestVersionFuture.join();
+ if (currentVersion == null || latestVersion == null)
+ throw new CompletionException(new IOException());
+
return latestVersion.getId() > currentVersion.getId() ? latestVersion : null;
});
}
-
+
+ /**
+ * get information about this version
+ * @return a future which can throw ioexpception
+ */
public CompletableFuture getCurrentVersion() {
return metadataProvider.getCurrentVersionMetadata();
}
-
+
+ /**
+ * download the latest available version
+ * @return a future which returns the downloaded file, it can also throw ioexpception or signatureexception
+ */
public CompletableFuture downloadLatestVersion() {
CompletableFuture latestVersionFuture = metadataProvider.getLatestVersionMetadata();
diff --git a/src/main/java/eu/m724/jarupdater/download/Downloader.java b/src/main/java/eu/m724/jarupdater/download/Downloader.java
index 4863214..0cccc0a 100644
--- a/src/main/java/eu/m724/jarupdater/download/Downloader.java
+++ b/src/main/java/eu/m724/jarupdater/download/Downloader.java
@@ -4,6 +4,19 @@ import java.io.File;
import java.util.concurrent.CompletableFuture;
public interface Downloader {
+ /**
+ * downloads a file and verifies it
+ * @param url
+ * @param sha256hex
+ * @return a future which can throw ioexception or signatureexception
+ */
public CompletableFuture downloadAndVerify(String url, String sha256hex);
+
+ /**
+ * moves source into destination
+ * @param source
+ * @param destination
+ * @return a future which can throw ioexception
+ */
public CompletableFuture install(File source, File destination);
}
diff --git a/src/main/java/eu/m724/jarupdater/download/SimpleDownloader.java b/src/main/java/eu/m724/jarupdater/download/SimpleDownloader.java
index 416b0d7..ca74fd3 100644
--- a/src/main/java/eu/m724/jarupdater/download/SimpleDownloader.java
+++ b/src/main/java/eu/m724/jarupdater/download/SimpleDownloader.java
@@ -64,7 +64,7 @@ public class SimpleDownloader implements Downloader {
return downloadedFile;
- } catch (IOException | NoSuchAlgorithmException | SignatureException e) {
+ } catch (IOException | NoSuchAlgorithmException | SignatureException e) { // TODO nosuchalgo should not be thrown
throw new CompletionException(e);
}
diff --git a/src/main/java/eu/m724/jarupdater/live/GiteaMetadataDAO.java b/src/main/java/eu/m724/jarupdater/live/GiteaMetadataDAO.java
new file mode 100644
index 0000000..8483fc6
--- /dev/null
+++ b/src/main/java/eu/m724/jarupdater/live/GiteaMetadataDAO.java
@@ -0,0 +1,81 @@
+package eu.m724.jarupdater.live;
+
+import java.io.IOException;
+import java.net.ProxySelector;
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.net.http.HttpClient.Redirect;
+import java.net.http.HttpResponse.BodyHandlers;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import com.google.gson.Gson;
+
+import eu.m724.jarupdater.object.Version;
+
+public class GiteaMetadataDAO implements MetadataDAO {
+ private String url;
+ private String branch;
+
+ public GiteaMetadataDAO(String url, String branch) {
+ this.url = url;
+ this.branch = branch;
+ }
+
+
+ @Override
+ public CompletableFuture> getChannels() {
+ String url = getFileUrl("channels.txt");
+
+ CompletableFuture> channelsFuture =
+ makeRequest(url).thenApply(response -> {
+ if (response.statusCode() != 200)
+ throw new CompletionException(new IOException("Server returned status code %d".formatted(response.statusCode())));
+
+ return response.body().lines().toList();
+ });
+
+ return channelsFuture;
+ }
+
+ @Override
+ public CompletableFuture getMetadata(String channel, String versionLabel) {
+ String url = getFileUrl(versionLabel, "meta-v1.json");
+
+ CompletableFuture metadataFuture =
+ makeRequest(url).thenApply(response -> {
+ if (response.statusCode() != 200)
+ throw new CompletionException(new IOException("Server returned status code %d".formatted(response.statusCode())));
+ // not throwing nosuchversionexecpion because it's not possible to tell if the server is broken or it really doesn't exist
+
+ Version version = new Gson().fromJson(response.body(), Version.class);
+ return version;
+ });
+
+ return metadataFuture;
+ }
+
+
+ private CompletableFuture> makeRequest(String url) {
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(URI.create(url))
+ .header("User-Agent", "ju/1") // jar updater v1
+ .build();
+
+ CompletableFuture> responseFuture =
+ HttpClient.newBuilder()
+ .followRedirects(Redirect.NORMAL)
+ .proxy(ProxySelector.getDefault()).build().
+ sendAsync(request, BodyHandlers.ofString());
+
+ return responseFuture;
+ }
+
+ private String getFileUrl(String... paths) {
+ return url + "/raw/branch/" + branch + "/data/" + String.join("/", paths);
+ }
+
+}
diff --git a/src/main/java/eu/m724/jarupdater/live/MetadataDAO.java b/src/main/java/eu/m724/jarupdater/live/MetadataDAO.java
index aef387c..99e26cb 100644
--- a/src/main/java/eu/m724/jarupdater/live/MetadataDAO.java
+++ b/src/main/java/eu/m724/jarupdater/live/MetadataDAO.java
@@ -1,11 +1,22 @@
package eu.m724.jarupdater.live;
-import java.util.ArrayList;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import eu.m724.jarupdater.object.Version;
public interface MetadataDAO {
- public CompletableFuture> getChannels();
- public CompletableFuture getMetadata(String channel, String version);
+ /**
+ * get available channels online
+ * @return a future which can throw ioexception
+ */
+ public CompletableFuture> getChannels();
+
+ /**
+ * get metadata of a version in channel
+ * @param channel
+ * @param versionLabel
+ * @return a future which can throw ioexcpeitons or nosuchfilexxception
+ */
+ public CompletableFuture getMetadata(String channel, String versionLabel);
}
diff --git a/src/main/java/eu/m724/jarupdater/live/MetadataFacade.java b/src/main/java/eu/m724/jarupdater/live/MetadataFacade.java
index 5402849..f212601 100644
--- a/src/main/java/eu/m724/jarupdater/live/MetadataFacade.java
+++ b/src/main/java/eu/m724/jarupdater/live/MetadataFacade.java
@@ -1,7 +1,7 @@
package eu.m724.jarupdater.live;
-import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.CompletableFuture;
import eu.m724.jarupdater.environment.Environment;
@@ -12,14 +12,14 @@ public class MetadataFacade {
private MetadataDAO metadataDao;
private HashMap> cache = new HashMap<>();
- private CompletableFuture> channels = null;
+ private CompletableFuture> channels = null;
public MetadataFacade(Environment environment, MetadataDAO metadataDao) {
this.environment = environment;
this.metadataDao = metadataDao;
}
- public CompletableFuture> getChannels() {
+ public CompletableFuture> getChannels() {
if (channels == null)
channels = metadataDao.getChannels();
diff --git a/src/main/java/eu/m724/jarupdater/object/NoSuchVersionException.java b/src/main/java/eu/m724/jarupdater/object/NoSuchVersionException.java
new file mode 100644
index 0000000..4ead06e
--- /dev/null
+++ b/src/main/java/eu/m724/jarupdater/object/NoSuchVersionException.java
@@ -0,0 +1,13 @@
+package eu.m724.jarupdater.object;
+
+import java.io.IOException;
+
+public class NoSuchVersionException extends IOException {
+
+ private static final long serialVersionUID = 8435964987348892266L;
+
+ public NoSuchVersionException(String version) {
+ super(version);
+ }
+
+}
diff --git a/src/main/java/eu/m724/jarupdater/object/Version.java b/src/main/java/eu/m724/jarupdater/object/Version.java
index 37dfdc0..0bdd276 100644
--- a/src/main/java/eu/m724/jarupdater/object/Version.java
+++ b/src/main/java/eu/m724/jarupdater/object/Version.java
@@ -5,7 +5,15 @@ public class Version {
* metadata file version
*/
public static final int SPEC = 1;
-
+
+ public Version(int id, long timestamp, String label, String fileUrl, String sha256) {
+ this.id = id;
+ this.timestamp = timestamp;
+ this.label = label;
+ this.fileUrl = fileUrl;
+ this.sha256 = sha256;
+ }
+
private int id;
private long timestamp;
private String label;
diff --git a/src/test/java/jarupdater/DownloaderTest.java b/src/test/java/jarupdater/DownloaderTest.java
new file mode 100644
index 0000000..5b2020c
--- /dev/null
+++ b/src/test/java/jarupdater/DownloaderTest.java
@@ -0,0 +1,46 @@
+package jarupdater;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.SignatureException;
+import java.util.concurrent.CompletionException;
+
+import org.junit.Test;
+
+import eu.m724.jarupdater.download.Downloader;
+
+public class DownloaderTest {
+ @Test
+ public void testDownloader() {
+ Downloader downloader = new MockDownloader();
+
+ File file = downloader.downloadAndVerify("good", "a9194ba3e955ba7482c6552894d4ca41b9bbafd86f4d90f3564c02fcb9e917c2").join();
+ assertNotNull(file);
+
+ try {
+ downloader.downloadAndVerify("invalid", "678af2539cb4156f4e092d5e80de23aed7d9355774697267979b94e5cceec1b2").join();
+ fail("this should have thrown");
+ } catch (CompletionException e) {
+ assert e.getCause() instanceof IOException;
+ }
+
+ try {
+ downloader.downloadAndVerify("bad", "1dd7d814fa99d923eee9e483c25a02346c47f84dbc160a1a9f87e9b051e77ee1").join();
+ fail("this should have thrown");
+ } catch (CompletionException e) {
+ assert e.getCause() instanceof SignatureException;
+ }
+
+ downloader.install(file, file).join();
+ try {
+ downloader.install(new File("ialsoexistfortesting"), file).join();
+ fail("this should have thrown");
+ } catch (CompletionException e) {
+ assert e.getCause() instanceof IOException;
+ }
+
+ }
+}
diff --git a/src/test/java/jarupdater/EnvironmentTest.java b/src/test/java/jarupdater/EnvironmentTest.java
new file mode 100644
index 0000000..9e0c9f9
--- /dev/null
+++ b/src/test/java/jarupdater/EnvironmentTest.java
@@ -0,0 +1,19 @@
+package jarupdater;
+
+import java.nio.file.Path;
+
+import org.junit.Test;
+
+import eu.m724.jarupdater.environment.ConstantEnvironment;
+import eu.m724.jarupdater.environment.Environment;
+
+public class EnvironmentTest {
+ @Test
+ public void testConstantEnvironment() {
+ Environment environment = new ConstantEnvironment("1.0", "stable", Path.of("idontexist"));
+
+ assert environment.getRunningVersion().equals("1.0");
+ assert environment.getChannel().equals("stable");
+ assert environment.getRunningJarFilePath().equals(Path.of("idontexist"));
+ }
+}
diff --git a/src/test/java/jarupdater/MetadataTest.java b/src/test/java/jarupdater/MetadataTest.java
new file mode 100644
index 0000000..1f2cb75
--- /dev/null
+++ b/src/test/java/jarupdater/MetadataTest.java
@@ -0,0 +1,44 @@
+package jarupdater;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.concurrent.CompletionException;
+
+import org.junit.Test;
+
+import eu.m724.jarupdater.environment.ConstantEnvironment;
+import eu.m724.jarupdater.environment.Environment;
+import eu.m724.jarupdater.live.MetadataDAO;
+import eu.m724.jarupdater.live.MetadataFacade;
+import eu.m724.jarupdater.object.Version;
+
+public class MetadataTest {
+ @Test
+ public void testMetadata() {
+ Environment environment = new ConstantEnvironment("1.0", "stable", Path.of("idontexist"));
+
+ MetadataDAO dao = new MockMetadataDAO();
+ MetadataFacade facade = new MetadataFacade(environment, dao);
+
+ assert facade.getChannels().join().contains(environment.getChannel());
+
+ Version cur = facade.getCurrentVersionMetadata().join();
+ Version lat = facade.getLatestVersionMetadata().join();
+
+ assert cur != null;
+ assert lat != null;
+
+ assertFalse(cur.equals(lat));
+ assertTrue(cur.getLabel().equals(environment.getRunningVersion()));
+ assertTrue(lat.getLabel().equals("1.1"));
+
+ try {
+ facade.getVersionMetadata("invalidversion").join();
+ fail("this should have thrown");
+ } catch (CompletionException e) {
+ assert e.getCause() instanceof IOException;
+ }
+ }
+}
diff --git a/src/test/java/jarupdater/MockDownloader.java b/src/test/java/jarupdater/MockDownloader.java
new file mode 100644
index 0000000..df28d94
--- /dev/null
+++ b/src/test/java/jarupdater/MockDownloader.java
@@ -0,0 +1,44 @@
+package jarupdater;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.SignatureException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import eu.m724.jarupdater.download.Downloader;
+
+public class MockDownloader implements Downloader {
+
+ @Override
+ public CompletableFuture downloadAndVerify(String url, String sha256hex) {
+ CompletableFuture future = null;
+
+ switch (url) {
+ case "good":
+ future = CompletableFuture.completedFuture(new File("ionlyexistfortesting"));
+ break;
+ case "invalid":
+ future = CompletableFuture.failedFuture(new CompletionException(new IOException()));
+ break;
+ case "bad":
+ future = CompletableFuture.failedFuture(new CompletionException(new SignatureException()));
+ break;
+ }
+
+ return future;
+ }
+
+ @Override
+ public CompletableFuture install(File source, File destination) {
+ CompletableFuture future = null;
+
+ if (source.getName().equals("ionlyexistfortesting"))
+ future = CompletableFuture.completedFuture(null);
+ else
+ future = CompletableFuture.failedFuture(new CompletionException(new IOException()));
+
+ return future;
+ }
+
+}
diff --git a/src/test/java/jarupdater/MockMetadataDAO.java b/src/test/java/jarupdater/MockMetadataDAO.java
new file mode 100644
index 0000000..82dd3e2
--- /dev/null
+++ b/src/test/java/jarupdater/MockMetadataDAO.java
@@ -0,0 +1,43 @@
+package jarupdater;
+
+import java.io.IOException;
+import java.nio.file.NoSuchFileException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+
+import eu.m724.jarupdater.live.MetadataDAO;
+import eu.m724.jarupdater.object.Version;
+
+public class MockMetadataDAO implements MetadataDAO {
+
+ @Override
+ public CompletableFuture> getChannels() {
+ List channels = Arrays.asList("stable", "beta", "alpha");
+ return CompletableFuture.completedFuture(channels);
+ }
+
+ @Override
+ public CompletableFuture getMetadata(String channel, String versionLabel) {
+ Version version = null;
+ String fileUrl = "http://127.0.0.1:17357/%s/%s.jar".formatted(channel, versionLabel);
+
+ switch (versionLabel) {
+ case "1.0":
+ version = new Version(1, 100, "1.0", fileUrl, "dd3822ed965b2820aa0800f04b86f26d0b656fca3b97ddd264046076f81e3a24");
+ break;
+ case "1.1":
+ version = new Version(2, 200, "1.1", fileUrl, "4d59994f607b89987d5bff430a4229edbbb045b3da9eaf1c63fa8e5fb208d824");
+ break;
+ case "latest":
+ version = new Version(2, 200, "1.1", fileUrl, "4d59994f607b89987d5bff430a4229edbbb045b3da9eaf1c63fa8e5fb208d824");
+ break;
+ default:
+ return CompletableFuture.failedFuture(new CompletionException(new IOException("no such version")));
+ }
+
+ return CompletableFuture.completedFuture(version);
+ }
+
+}