aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--build.gradle.kts15
-rw-r--r--src/main/java/moe/nea/libautoupdate/CurrentVersion.java66
-rw-r--r--src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java12
-rw-r--r--src/main/java/moe/nea/libautoupdate/GistSource.java3
-rw-r--r--src/main/java/moe/nea/libautoupdate/GithubReleaseUpdateSource.java136
-rw-r--r--src/main/java/moe/nea/libautoupdate/JsonUpdateSource.java23
-rw-r--r--src/main/java/moe/nea/libautoupdate/Main.java9
-rw-r--r--src/main/java/moe/nea/libautoupdate/PotentialUpdate.java9
-rw-r--r--src/main/java/moe/nea/libautoupdate/UpdateData.java3
-rw-r--r--src/main/java/moe/nea/libautoupdate/UpdateSource.java7
-rw-r--r--src/main/java/moe/nea/libautoupdate/UpdateUtils.java26
-rw-r--r--updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java4
13 files changed, 280 insertions, 45 deletions
diff --git a/README.md b/README.md
index 76421f0..9d77d4f 100644
--- a/README.md
+++ b/README.md
@@ -32,7 +32,7 @@ UpdateContext updateContext=new UpdateContext(
);
```
-You will have to specify an update source (currently either a `gistSource` or your own implementation),
+You will have to specify an update source,
an update target (the file to replace), the version that is currently being ran, and a string id to prevent
files from being overwritten.
@@ -42,7 +42,7 @@ application.
### Sources
- - GistSource
+ - Gist Source
Uses a gist with multiple (or just one) files called `<upstream>.json` in the format
```json5
@@ -53,6 +53,14 @@ Uses a gist with multiple (or just one) files called `<upstream>.json` in the fo
}
```
+ - GitHub Releases Source
+
+Uses a GitHub release to either source a pre-release or full-release JAR. This release source does not support hashes or
+newer-than-latest local versions. The current version also has to be the tag name, unlike the gist source which can have
+any version type. The GitHub release needs to have a tag that is also present in the jar itself, and there should only
+be one JAR in each GitHub release. Subclasses of this source may provide custom logic for choosing the jar / version.
+
+
### Targets
- DeleteAndSaveInSameFolder
diff --git a/build.gradle.kts b/build.gradle.kts
index d323f7a..7f078c6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -1,11 +1,13 @@
+
plugins {
`maven-publish`
signing
java
+ id("io.freefair.lombok") version "6.5.1"
}
group = "moe.nea"
-version = "0.1.0"
+version = "1.0.0"
allprojects {
apply(plugin = "java")
@@ -32,11 +34,18 @@ project(":updater") {
}
dependencies {
- compileOnly("org.projectlombok:lombok:1.18.24")
- annotationProcessor("org.projectlombok:lombok:1.18.24")
+ @Suppress("VulnerableLibrariesLocal")
+ // We use this version of gson, because this is intended to be used for minecraft 1.8.9 (which bundles that gson version)
implementation("com.google.code.gson:gson:2.2.4")
}
+java {
+ withJavadocJar()
+ withSourcesJar()
+}
+tasks.javadoc {
+ isFailOnError = false
+}
tasks.processResources {
val updateJar = tasks.getByPath(":updater:jar")
diff --git a/src/main/java/moe/nea/libautoupdate/CurrentVersion.java b/src/main/java/moe/nea/libautoupdate/CurrentVersion.java
index 8c0837e..e6e4ff8 100644
--- a/src/main/java/moe/nea/libautoupdate/CurrentVersion.java
+++ b/src/main/java/moe/nea/libautoupdate/CurrentVersion.java
@@ -1,21 +1,73 @@
package moe.nea.libautoupdate;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonPrimitive;
+
/**
* Provider interface for getting the current version of this jar.
*/
public interface CurrentVersion {
+
/**
- * @return the version number
+ * Create a {@link CurrentVersion} that compares only numbers. (Other version types will be assumed to be newer.)
+ *
+ * @param number the current version number
*/
- int getCurrentVersionNumber();
+ static CurrentVersion of(int number) {
+ return new CurrentVersion() {
+ @Override
+ public String display() {
+ return String.valueOf(number);
+ }
+
+ @Override
+ public boolean isOlderThan(JsonElement element) {
+ if (!element.isJsonPrimitive()) return true;
+ JsonPrimitive prim = element.getAsJsonPrimitive();
+ if (!prim.isNumber()) return true;
+ return prim.getAsInt() > number;
+ }
+
+ @Override
+ public String toString() {
+ return "VersionNumber (" + number + ")";
+ }
+ };
+ }
/**
- * Create a constant {@link CurrentVersion}
+ * Create a {@link CurrentVersion} that uses git tag names. Any difference in tag name will be treated as newer.
*
- * @param number the constant version number
- * @return
+ * @param tagName the current tag name
*/
- static CurrentVersion of(int number) {
- return () -> number;
+ static CurrentVersion ofTag(String tagName) {
+ return new CurrentVersion() {
+ @Override
+ public String display() {
+ return tagName;
+ }
+
+ @Override
+ public boolean isOlderThan(JsonElement element) {
+ return !new JsonPrimitive(tagName).equals(element);
+ }
+
+ @Override
+ public String toString() {
+ return "VersionTag (" + tagName + ")";
+ }
+ };
}
+
+ /**
+ * @return a user-friendly representation of this version
+ */
+ String display();
+
+ /**
+ * Compare to another version, represented as JSON.
+ *
+ * @return true, if this version is older than the other version and an update to that version should occur.
+ */
+ boolean isOlderThan(JsonElement element);
}
diff --git a/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java b/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java
index 0b1eaee..971a5de 100644
--- a/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java
+++ b/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java
@@ -3,6 +3,7 @@ package moe.nea.libautoupdate;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
+import java.util.UUID;
/**
* A Utility class for setting up the exit hook, which then launches the next stage (postexit) using the same java runtime.
@@ -13,6 +14,8 @@ public class ExitHookInvoker {
private static List<UpdateAction> actions;
private static File updaterJar;
private static boolean cancelled = false;
+ private static String identifer;
+ private static UUID uuid;
/**
* Set up the exit hook to run post exit actions.
@@ -20,15 +23,19 @@ public class ExitHookInvoker {
* <p><b>N.B.:</b> Calling this multiple times will only invoke the last set of actions.
* In case of multiple updates the update actions should be joined in the same list.</p>
*
+ * @param identifier the identifier for this updater for logging purposes
+ * @param uuid the uuid of this update for logging purposes
* @param updaterJar the extracted updater jar
* @param actions the actions to execute
*/
- public static synchronized void setExitHook(File updaterJar, List<UpdateAction> actions) {
+ public static synchronized void setExitHook(String identifier, UUID uuid, File updaterJar, List<UpdateAction> actions) {
if (!isExitHookRegistered) {
Runtime.getRuntime().addShutdownHook(new Thread(ExitHookInvoker::runExitHook));
isExitHookRegistered = true;
}
+ ExitHookInvoker.identifer = identifier;
+ ExitHookInvoker.uuid = uuid;
ExitHookInvoker.cancelled = false;
ExitHookInvoker.actions = actions;
ExitHookInvoker.updaterJar = updaterJar;
@@ -51,6 +58,9 @@ public class ExitHookInvoker {
arguments.add("-jar");
arguments.add(updaterJar.getAbsolutePath());
+ arguments.add(identifer);
+ arguments.add(String.valueOf(uuid));
+
for (UpdateAction action : actions) {
action.encode(arguments);
}
diff --git a/src/main/java/moe/nea/libautoupdate/GistSource.java b/src/main/java/moe/nea/libautoupdate/GistSource.java
index a8ae5df..c2a776b 100644
--- a/src/main/java/moe/nea/libautoupdate/GistSource.java
+++ b/src/main/java/moe/nea/libautoupdate/GistSource.java
@@ -1,6 +1,7 @@
package moe.nea.libautoupdate;
import lombok.EqualsAndHashCode;
+import lombok.NonNull;
import lombok.Value;
import java.util.concurrent.CompletableFuture;
@@ -8,7 +9,9 @@ import java.util.concurrent.CompletableFuture;
@Value
@EqualsAndHashCode(callSuper = false)
public class GistSource extends JsonUpdateSource {
+ @NonNull
String owner;
+ @NonNull
String gistId;
private static final String GIST_RAW_URL = "https://gist.githubusercontent.com/%s/%s/raw/%s.json";
diff --git a/src/main/java/moe/nea/libautoupdate/GithubReleaseUpdateSource.java b/src/main/java/moe/nea/libautoupdate/GithubReleaseUpdateSource.java
new file mode 100644
index 0000000..067ea4b
--- /dev/null
+++ b/src/main/java/moe/nea/libautoupdate/GithubReleaseUpdateSource.java
@@ -0,0 +1,136 @@
+package moe.nea.libautoupdate;
+
+import com.google.gson.JsonPrimitive;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.reflect.TypeToken;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.NonNull;
+
+import java.util.Comparator;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+/**
+ * Update source pulling from a GitHub repositories releases.
+ * <p>
+ * The stream {@code pre} is dedicated to pre-releases (or full releases, if the newest full release is newer than
+ * the newest pre releases).
+ * The stream {@code full} is dedicated to only full releases.
+ * Override {@link #selectUpdate(String, List)} to change this behaviour.
+ * </p>
+ * <p>
+ * By default the first JAR that is in that release will be selected.
+ * Override {@link #findAsset(GithubRelease)} to change this behaviour
+ * </p>
+ * <p>This {@link UpdateSource} does not support newer than latest releases, since it uses the git tag as update version, and does not support hash checking.</p>
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+public class GithubReleaseUpdateSource extends JsonUpdateSource {
+ @NonNull
+ final String owner;
+ @NonNull
+ final String repository;
+
+ /**
+ * Find an update out of the list of releases from a GitHub repository.
+ *
+ * @param updateStream the update stream to find the update for
+ * @param releases the list of releases for this GitHub repository
+ * @return the latest update that matches this update stream
+ */
+ protected UpdateData selectUpdate(String updateStream, List<GithubRelease> releases) {
+ if (Objects.equals("pre", updateStream)) {
+ return findLatestRelease(releases.stream().filter(it -> !it.isDraft()).collect(Collectors.toList()));
+ }
+ if (Objects.equals("full", updateStream)) {
+ return findLatestRelease(releases.stream().filter(it -> !it.isDraft() && !it.isPrerelease()).collect(Collectors.toList()));
+ }
+ return null;
+ }
+
+ /**
+ * Find the matching Jar from a GitHub release.
+ *
+ * @param release a release containing assets
+ * @return an update data referencing one of the assets of that release
+ */
+ protected UpdateData findAsset(GithubRelease release) {
+ if (release.getAssets() == null) return null;
+ return release.getAssets().stream()
+ .filter(it -> Objects.equals(it.getContentType(), "application/x-java-archive") && it.getBrowserDownloadUrl() != null)
+ .map(it -> new UpdateData(
+ release.getName() == null ? release.getTagName() : release.getName(),
+ new JsonPrimitive(release.getTagName()),
+ null,
+ it.getBrowserDownloadUrl()
+ ))
+ .findFirst().orElse(null);
+ }
+
+ /**
+ * Find the latest release out of a list of releases that are valid for an updateStream.
+ * Uses {@link #findAsset(GithubRelease)} to find which jar file to use.
+ *
+ * @param validReleases the list of valid releases
+ * @return the latest release (or null)
+ */
+ protected UpdateData findLatestRelease(Iterable<GithubRelease> validReleases) {
+ return StreamSupport.stream(validReleases.spliterator(), false)
+ .max(Comparator.comparing(GithubRelease::getPublishedAt))
+ .map(this::findAsset)
+ .orElse(null);
+ }
+
+
+ private String getReleaseApiUrl() {
+ return String.format("https://api.github.com/repos/%s/%s/releases", owner, repository);
+ }
+
+ @Override
+ public CompletableFuture<UpdateData> checkUpdate(String updateStream) {
+ CompletableFuture<List<GithubRelease>> releases = getJsonFromURL(getReleaseApiUrl(), new TypeToken<List<GithubRelease>>() {
+ }.getType());
+ return releases.thenApply(it -> it == null ? null : selectUpdate(updateStream, it));
+ }
+
+ /**
+ * A data class representing the GitHub API response to
+ * api.github.com/repos/a/b/releases
+ */
+ @Data
+ public static class GithubRelease {
+ @SerializedName("tag_name")
+ String tagName;
+ @SerializedName("target_commitish")
+ String targetCommitish;
+ String name;
+ boolean draft = false;
+ boolean prerelease = false;
+ @SerializedName("created_at")
+ Date created_at;
+ @SerializedName("published_at")
+ Date publishedAt;
+ int id = 0;
+ List<Download> assets;
+ String body;
+ @SerializedName("html_url")
+ String htmlUrl;
+
+ @Data
+ public static class Download {
+ int id = 0;
+ String name;
+ @SerializedName("content_type")
+ String contentType;
+ String label;
+ @SerializedName("browser_download_url")
+ String browserDownloadUrl;
+ }
+ }
+}
diff --git a/src/main/java/moe/nea/libautoupdate/JsonUpdateSource.java b/src/main/java/moe/nea/libautoupdate/JsonUpdateSource.java
index ffbfa9a..95c0b9f 100644
--- a/src/main/java/moe/nea/libautoupdate/JsonUpdateSource.java
+++ b/src/main/java/moe/nea/libautoupdate/JsonUpdateSource.java
@@ -1,31 +1,18 @@
package moe.nea.libautoupdate;
import com.google.gson.Gson;
-import lombok.val;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
+import java.lang.reflect.Type;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.CompletionException;
+
public abstract class JsonUpdateSource implements UpdateSource {
- private static final Gson gson = new Gson();
protected Gson getGson() {
- return gson;
+ return UpdateUtils.gson;
}
- protected <T> CompletableFuture<T> getJsonFromURL(String url, Class<T> clazz) {
- return CompletableFuture.supplyAsync(() -> {
- try {
- try (val is = new URL(url).openStream()) {
- return getGson().fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), clazz);
- }
- } catch (IOException e) {
- throw new CompletionException(e);
- }
- });
+ protected <T> CompletableFuture<T> getJsonFromURL(String url, Type clazz) {
+ return UpdateUtils.httpGet(url, getGson(), clazz);
}
}
diff --git a/src/main/java/moe/nea/libautoupdate/Main.java b/src/main/java/moe/nea/libautoupdate/Main.java
index 5d98df1..3043348 100644
--- a/src/main/java/moe/nea/libautoupdate/Main.java
+++ b/src/main/java/moe/nea/libautoupdate/Main.java
@@ -4,16 +4,17 @@ public class Main {
public static void main(String[] args) {
UpdateContext updater = new UpdateContext(
- UpdateSource.gistSource("romangraef", "9b62fe32bc41c09d2d7e2d3153f14ee8"),
+ UpdateSource.githubUpdateSource("NotEnoughUpdates", "NotEnoughUpdates"),
UpdateTarget.deleteAndSaveInTheSameFolder(Main.class),
- CurrentVersion.of(10000),
+ CurrentVersion.ofTag("v2.1.1-pre1"),
"test"
);
updater.cleanup();
System.out.println("Update cleaned");
System.out.println("Created update context: " + updater);
- updater.checkUpdate("stable").thenCompose(it -> {
- System.out.println("Checked for update on stable: " + it);
+ String stream = "pre";
+ updater.checkUpdate(stream).thenCompose(it -> {
+ System.out.println("Checked for update on " + stream + ": " + it);
System.out.println("Can update: " + it.isUpdateAvailable());
System.out.println("Executing update.");
return it.launchUpdate();
diff --git a/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java b/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java
index febaa80..33d4026 100644
--- a/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java
+++ b/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java
@@ -39,7 +39,7 @@ public class PotentialUpdate {
*/
public boolean isUpdateAvailable() {
if (update == null) return false;
- return update.getVersionNumber() > context.getCurrentVersion().getCurrentVersionNumber();
+ return context.getCurrentVersion().isOlderThan(update.getVersionNumber());
}
private File getFile(String name) {
@@ -84,7 +84,7 @@ public class PotentialUpdate {
}
try (val check = new FileInputStream(getUpdateJarStorage())) {
val updateSha = UpdateUtils.sha256sum(check);
- if (!update.getSha256().equalsIgnoreCase(updateSha)) {
+ if (update.getSha256() != null && !update.getSha256().equalsIgnoreCase(updateSha)) {
throw new UpdateException(
"Hash of downloaded file " + getUpdateJarStorage() +
" (" + updateSha + ") does not match expected hash of " +
@@ -107,7 +107,10 @@ public class PotentialUpdate {
*/
public void executeUpdate() throws IOException {
prepareUpdate();
- ExitHookInvoker.setExitHook(getFile("updater.jar"),
+ ExitHookInvoker.setExitHook(
+ getContext().getIdentifier(),
+ getUpdateUUID(),
+ getFile("updater.jar"),
context.getTarget().generateUpdateActions(this));
}
diff --git a/src/main/java/moe/nea/libautoupdate/UpdateData.java b/src/main/java/moe/nea/libautoupdate/UpdateData.java
index 276ae5f..2ef476a 100644
--- a/src/main/java/moe/nea/libautoupdate/UpdateData.java
+++ b/src/main/java/moe/nea/libautoupdate/UpdateData.java
@@ -1,5 +1,6 @@
package moe.nea.libautoupdate;
+import com.google.gson.JsonElement;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -12,7 +13,7 @@ import java.net.URL;
@AllArgsConstructor
public class UpdateData {
String versionName;
- int versionNumber;
+ JsonElement versionNumber;
String sha256;
String download;
diff --git a/src/main/java/moe/nea/libautoupdate/UpdateSource.java b/src/main/java/moe/nea/libautoupdate/UpdateSource.java
index 41765a5..9a0e7ae 100644
--- a/src/main/java/moe/nea/libautoupdate/UpdateSource.java
+++ b/src/main/java/moe/nea/libautoupdate/UpdateSource.java
@@ -11,6 +11,13 @@ public interface UpdateSource {
}
/**
+ * Create a {@link GithubReleaseUpdateSource}.
+ */
+ static UpdateSource githubUpdateSource(String owner, String repository) {
+ return new GithubReleaseUpdateSource(owner, repository);
+ }
+
+ /**
* Check for updates in the given update stream.
*
* @param updateStream the update stream to check for updates.
diff --git a/src/main/java/moe/nea/libautoupdate/UpdateUtils.java b/src/main/java/moe/nea/libautoupdate/UpdateUtils.java
index ce09c0b..f7319ca 100644
--- a/src/main/java/moe/nea/libautoupdate/UpdateUtils.java
+++ b/src/main/java/moe/nea/libautoupdate/UpdateUtils.java
@@ -1,15 +1,16 @@
package moe.nea.libautoupdate;
+import com.google.gson.Gson;
import lombok.val;
import lombok.var;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
+import java.io.*;
+import java.lang.reflect.Type;
import java.math.BigInteger;
import java.net.URI;
import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -18,11 +19,15 @@ import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
public class UpdateUtils {
private UpdateUtils() {
}
+ static final Gson gson = new Gson();
+
public static File getJarFileContainingClass(Class<?> clazz) {
val location = clazz.getProtectionDomain().getCodeSource().getLocation();
if (location == null)
@@ -82,5 +87,18 @@ public class UpdateUtils {
}
});
}
+
+ public static <T> CompletableFuture<T> httpGet(String url, Gson gson, Type clazz) {
+ return CompletableFuture.supplyAsync(() -> {
+ try {
+ try (val is = new URL(url).openStream()) {
+ return gson.fromJson(new InputStreamReader(is, StandardCharsets.UTF_8), clazz);
+ }
+ } catch (IOException e) {
+ throw new CompletionException(e);
+ }
+ });
+ }
+
}
diff --git a/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java b/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java
index cf9a820..33db32e 100644
--- a/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java
+++ b/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java
@@ -14,8 +14,8 @@ public class PostExitMain {
PrintStream printStream = new PrintStream(new FileOutputStream(outputFile, true));
System.setErr(printStream);
System.setOut(printStream);
-
- for (int i = 0; i < args.length; i++) {
+ System.out.println("Starting update (with identifier " + args[0] + " and uuid " + args[1] + ")");
+ for (int i = 2; i < args.length; i++) {
switch (args[i].intern()) {
case "delete":
File file = unlockedFile(args[++i]);