diff options
8 files changed, 136 insertions, 15 deletions
diff --git a/src/main/java/moe/nea/libautoupdate/CurrentVersion.java b/src/main/java/moe/nea/libautoupdate/CurrentVersion.java index 52b8372..8c0837e 100644 --- a/src/main/java/moe/nea/libautoupdate/CurrentVersion.java +++ b/src/main/java/moe/nea/libautoupdate/CurrentVersion.java @@ -1,8 +1,20 @@ package moe.nea.libautoupdate; +/** + * Provider interface for getting the current version of this jar. + */ public interface CurrentVersion { + /** + * @return the version number + */ int getCurrentVersionNumber(); + /** + * Create a constant {@link CurrentVersion} + * + * @param number the constant version number + * @return + */ static CurrentVersion of(int number) { return () -> number; } diff --git a/src/main/java/moe/nea/libautoupdate/DeleteAndSaveInSameFolderUpdateTarget.java b/src/main/java/moe/nea/libautoupdate/DeleteAndSaveInSameFolderUpdateTarget.java index 3e07f45..737fe79 100644 --- a/src/main/java/moe/nea/libautoupdate/DeleteAndSaveInSameFolderUpdateTarget.java +++ b/src/main/java/moe/nea/libautoupdate/DeleteAndSaveInSameFolderUpdateTarget.java @@ -7,8 +7,14 @@ import java.io.File; import java.util.Arrays; import java.util.List; +/** + * Create {@link UpdateTarget} that deletes the specified Jar, and saves the new Jar in the same folder with the name specified by the download URL. + */ @Value public class DeleteAndSaveInSameFolderUpdateTarget implements UpdateTarget { + /** + * The file that will be replaced. + */ File file; @SneakyThrows diff --git a/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java b/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java index 55436a2..0b1eaee 100644 --- a/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java +++ b/src/main/java/moe/nea/libautoupdate/ExitHookInvoker.java @@ -4,6 +4,9 @@ import java.io.File; import java.util.ArrayList; import java.util.List; +/** + * A Utility class for setting up the exit hook, which then launches the next stage (postexit) using the same java runtime. + */ public class ExitHookInvoker { private static boolean isExitHookRegistered = false; @@ -11,7 +14,15 @@ public class ExitHookInvoker { private static File updaterJar; private static boolean cancelled = false; - + /** + * Set up the exit hook to run post exit actions. + * + * <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 updaterJar the extracted updater jar + * @param actions the actions to execute + */ public static synchronized void setExitHook(File updaterJar, List<UpdateAction> actions) { if (!isExitHookRegistered) { Runtime.getRuntime().addShutdownHook(new Thread(ExitHookInvoker::runExitHook)); @@ -23,7 +34,10 @@ public class ExitHookInvoker { ExitHookInvoker.updaterJar = updaterJar; } - public static synchronized void cancelUpdate() { + /** + * Cancel the exit hook, invalidating any previous calls to {@link #setExitHook} + */ + public static synchronized void cancelExitHook() { cancelled = true; } diff --git a/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java b/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java index 2e6142d..febaa80 100644 --- a/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java +++ b/src/main/java/moe/nea/libautoupdate/PotentialUpdate.java @@ -14,15 +14,29 @@ import java.util.concurrent.CompletionException; @Value public class PotentialUpdate { + /** + * The update data of this update. + */ UpdateData update; + /** + * The context of this update. + */ UpdateContext context; + /** + * A UUID so that each update does not conflict which each other. + */ UUID updateUUID = UUID.randomUUID(); + /** + * @return the directory in which update data gets stored for the post exit stage. + */ public File getUpdateDirectory() { return new File(".autoupdates", context.getIdentifier() + "/" + updateUUID); } - + /** + * @return true if an update exists and has a higher version number than our current version. + */ public boolean isUpdateAvailable() { if (update == null) return false; return update.getVersionNumber() > context.getCurrentVersion().getCurrentVersionNumber(); @@ -33,16 +47,25 @@ public class PotentialUpdate { return new File(getUpdateDirectory(), name); } + /** + * @return the location where the updated jar will be stored. + */ public File getUpdateJarStorage() { return getFile("next.jar"); } + /** + * @return the filename of the updated jar, as specified by the {@link UpdateData#getDownload()} + * @throws MalformedURLException if {@link #update} contains an invalid download url. + */ public String getFileName() throws MalformedURLException { val split = update.getDownloadAsURL().getPath().split("/"); return split[split.length - 1]; } - + /** + * Extracts the updater jar (for the post exit stage) into the storage directory. + */ public void extractUpdater() throws IOException { val file = getFile("updater.jar"); try (val from = getClass().getResourceAsStream("/updater.jar"); @@ -51,6 +74,9 @@ public class PotentialUpdate { } } + /** + * Download the updated jar into the storage directory. + */ public void downloadUpdate() throws IOException { try (val from = update.getDownloadAsURL().openStream(); val to = new FileOutputStream(getUpdateJarStorage())) { @@ -67,19 +93,29 @@ public class PotentialUpdate { } } + /** + * Prepare the layout of the storage directory. + */ public void prepareUpdate() throws IOException { extractUpdater(); downloadUpdate(); } - + /** + * Execute the update. + * This is done by first preparing the storage directory using {@link #prepareUpdate()} and then setting the {@link ExitHookInvoker} + */ public void executeUpdate() throws IOException { prepareUpdate(); ExitHookInvoker.setExitHook(getFile("updater.jar"), context.getTarget().generateUpdateActions(this)); } - + /** + * Execute the update in another thread. + * + * @return a future which is completed when the update is downloaded and hooked on exit. + */ public CompletableFuture<Void> launchUpdate() { return CompletableFuture.supplyAsync(() -> { try { diff --git a/src/main/java/moe/nea/libautoupdate/UpdateContext.java b/src/main/java/moe/nea/libautoupdate/UpdateContext.java index fa8b8d5..35fd021 100644 --- a/src/main/java/moe/nea/libautoupdate/UpdateContext.java +++ b/src/main/java/moe/nea/libautoupdate/UpdateContext.java @@ -8,14 +8,31 @@ import java.io.File; import java.io.IOException; import java.util.concurrent.CompletableFuture; +/** + * The update context holds information about your application, and it's update process. + */ @Value public class UpdateContext { + /** + * The source from which to pull updates. + */ @NonNull UpdateSource source; + /** + * The update target which specifies how to install an update. + */ @NonNull UpdateTarget target; + /** + * The version of this application + */ @NonNull CurrentVersion currentVersion; + /** + * An identifier for this application, as to not collide with other applications using this update lib. + */ @NonNull String identifier; - + /** + * Delete remnants of previous updates. + */ public void cleanup() { File file = new File(".autoupdates", identifier).getAbsoluteFile(); try { @@ -25,6 +42,12 @@ public class UpdateContext { } } + /** + * Check for an update. + * + * @param updateStream the update stream to check. + * @return a future that resolves to an update of this stream, or null. + */ public CompletableFuture<PotentialUpdate> checkUpdate(String updateStream) { return source.checkUpdate(updateStream) .thenApply(it -> new PotentialUpdate(it, this)); diff --git a/src/main/java/moe/nea/libautoupdate/UpdateSource.java b/src/main/java/moe/nea/libautoupdate/UpdateSource.java index 1407063..41765a5 100644 --- a/src/main/java/moe/nea/libautoupdate/UpdateSource.java +++ b/src/main/java/moe/nea/libautoupdate/UpdateSource.java @@ -2,10 +2,19 @@ package moe.nea.libautoupdate; import java.util.concurrent.CompletableFuture; +/** + * UpdateSource is an interface to check for updates in an update stream. + */ public interface UpdateSource { static UpdateSource gistSource(String owner, String gistId) { return new GistSource(owner, gistId); } + /** + * Check for updates in the given update stream. + * + * @param updateStream the update stream to check for updates. + * @return A future that completes with the next {@link UpdateData} or null, if there is no update. + */ CompletableFuture<UpdateData> checkUpdate(String updateStream); } diff --git a/src/main/java/moe/nea/libautoupdate/UpdateTarget.java b/src/main/java/moe/nea/libautoupdate/UpdateTarget.java index fcaaa3e..3a60963 100644 --- a/src/main/java/moe/nea/libautoupdate/UpdateTarget.java +++ b/src/main/java/moe/nea/libautoupdate/UpdateTarget.java @@ -1,16 +1,32 @@ package moe.nea.libautoupdate; -import java.io.File; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.*; import java.util.List; +/** + * Interface for generating post download actions for installing the update Jar. + */ public interface UpdateTarget { List<UpdateAction> generateUpdateActions(PotentialUpdate update); + /** + * Create + * + * @param containedClass + * @return + */ static UpdateTarget replaceJar(Class<?> containedClass) { File file = UpdateUtils.getJarFileContainingClass(containedClass); return new ReplaceJarUpdateTarget(file); } + + /** + * Create an update target that deletes the Jar containing the specified class, and saves the new Jar in the same + */ static UpdateTarget deleteAndSaveInTheSameFolder(Class<?> containedClass) { File file = UpdateUtils.getJarFileContainingClass(containedClass); return new DeleteAndSaveInSameFolderUpdateTarget(file); 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 d143fee..cf9a820 100644 --- a/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java +++ b/updater/src/main/java/moe/nea/libautoupdate/postexit/PostExitMain.java @@ -8,7 +8,7 @@ import java.nio.file.Files; import java.nio.file.StandardCopyOption; public class PostExitMain { - public static void main(String[] args) throws IOException { + public static void main(String[] args) throws IOException, InterruptedException { File outputFile = new File(".autoupdates", "postexit.log"); outputFile.getParentFile().mkdirs(); PrintStream printStream = new PrintStream(new FileOutputStream(outputFile, true)); @@ -18,13 +18,15 @@ public class PostExitMain { for (int i = 0; i < args.length; i++) { switch (args[i].intern()) { case "delete": - File file = new File(args[++i]); + File file = unlockedFile(args[++i]); System.out.println("Deleting " + file); - file.delete(); + if (!file.delete()) { + System.out.println("Failed to delete " + file); + } break; case "move": - File from = new File(args[++i]); - File to = new File(args[++i]); + File from = unlockedFile(args[++i]); + File to = unlockedFile(args[++i]); System.out.println("Moving " + from + " to " + to); // Use Files.move instead of File.renameTo, since renameTo is not well-defined. Files.move(from.toPath(), to.toPath(), StandardCopyOption.REPLACE_EXISTING); @@ -36,11 +38,14 @@ public class PostExitMain { } } - public void unlockedFile(File file) throws InterruptedException { - while (!file.exists() || !file.renameTo(file)) { + public static File unlockedFile(String name) throws InterruptedException { + File file = new File(name); + while (file.exists() && !file.renameTo(file)) { + System.out.println("Waiting on a process to relinquish access to " + file); Thread.sleep(1000L); } file.getParentFile().mkdirs(); + return file; } } |