From 96c8777ac2a3fbc01f288ff31ae09b264cf617e5 Mon Sep 17 00:00:00 2001 From: Minecon724 Date: Thu, 20 Jun 2024 16:21:13 +0200 Subject: [PATCH] finish updater --- .../realweather/commands/UpdateCommand.java | 41 +++++++++++++---- .../realweather/updater/UpdateDownloader.java | 44 +++++++++++-------- .../eu/m724/realweather/updater/Updater.java | 42 ++++++++++++++++-- .../updater/metadata/VersionMetadata.java | 4 +- 4 files changed, 98 insertions(+), 33 deletions(-) diff --git a/src/main/java/eu/m724/realweather/commands/UpdateCommand.java b/src/main/java/eu/m724/realweather/commands/UpdateCommand.java index f303e8b..edff101 100644 --- a/src/main/java/eu/m724/realweather/commands/UpdateCommand.java +++ b/src/main/java/eu/m724/realweather/commands/UpdateCommand.java @@ -24,15 +24,38 @@ public class UpdateCommand { sender.sendMessage("Please wait"); CompletableFuture latestFuture = updater.getLatestVersion(); - latestFuture.thenAccept(metadata -> { - if (metadata != null) { - sender.sendMessage("An update is available!"); - sender.sendMessage("RealWeather %s released %s".formatted(metadata.label, metadata.getFormattedDate())); - sender.sendMessage("To download: /rwadmin update download"); - } else { - sender.sendMessage("No new updates"); // TODO color - } - }); + if (args.length == 0) { + latestFuture.thenAccept(metadata -> { + if (metadata != null) { + sender.sendMessage("An update is available!"); + sender.sendMessage("RealWeather %s released %s".formatted(metadata.label, metadata.getFormattedDate())); + sender.sendMessage("To download: /rwadmin update download"); + } else { + sender.sendMessage("No new updates"); // TODO color + } + }); + } else { + String action = args[1]; // remember this function is proxied + + if (action.equals("download")) { + sender.sendMessage("Started download"); + + updater.downloadUpdate().handle((file, ex) -> { + sender.sendMessage("Download failed. See console for details."); + ex.printStackTrace(); + return null; + }).thenAccept(file -> { + if (file != null) + sender.sendMessage("Download finished, install with /rwadmin update install"); + }); + + } else if (action.equals("install")) { + if (updater.installUpdate()) + sender.sendMessage("Update installed, restart server to apply."); + else sender.sendMessage("Update not installed, see console."); + } else return false; + } + return true; } diff --git a/src/main/java/eu/m724/realweather/updater/UpdateDownloader.java b/src/main/java/eu/m724/realweather/updater/UpdateDownloader.java index 93eaab0..d17add4 100644 --- a/src/main/java/eu/m724/realweather/updater/UpdateDownloader.java +++ b/src/main/java/eu/m724/realweather/updater/UpdateDownloader.java @@ -7,37 +7,48 @@ import java.io.IOException; import java.io.InputStream; import java.net.ProxySelector; import java.net.URI; -import java.net.URISyntaxException; 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.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.SignatureException; import java.util.Arrays; import java.util.HexFormat; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import org.bukkit.plugin.Plugin; - public class UpdateDownloader { - private Plugin plugin; - private File getRunningPluginFile() { - try { - return new File(plugin.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } + /** + * + * @param url + * @param sha256hex + * @return + * @throws SignatureException if signature doesnt match + */ + public CompletableFuture downloadAndVerify(String url, String sha256hex) { + return download(url).thenApply(file -> { + try { + byte[] hash = computeFileSha256Hash(file); + boolean matches = compareBytesHex(hash, sha256hex); + + if (!matches) { + // TODO clean? + throw new SignatureException(); + } + + return file; + } catch (IOException | SignatureException e) { + throw new CompletionException(e); + } + }); } - private CompletableFuture download(String url) { + private CompletableFuture download(String url) { // TODO progress? HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(url)) @@ -73,11 +84,6 @@ public class UpdateDownloader { return fileFuture; } - private void install(File file) throws IOException { - // TODO what if we changed File to Path and every ref in this file - Files.move(file.toPath(), getRunningPluginFile().toPath(), StandardCopyOption.REPLACE_EXISTING); - } - private byte[] computeFileSha256Hash(File file) throws IOException { MessageDigest digest = null; try { diff --git a/src/main/java/eu/m724/realweather/updater/Updater.java b/src/main/java/eu/m724/realweather/updater/Updater.java index d89287b..8a4b448 100644 --- a/src/main/java/eu/m724/realweather/updater/Updater.java +++ b/src/main/java/eu/m724/realweather/updater/Updater.java @@ -1,5 +1,10 @@ package eu.m724.realweather.updater; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -23,9 +28,13 @@ import net.md_5.bungee.api.chat.ComponentBuilder; public class Updater extends BukkitRunnable implements Listener { private UpdaterConfig updaterConfig; private MetadataRetriever metadataRetriever; + private UpdateDownloader updateDownloader; private Plugin plugin = GlobalConstants.getPlugin(); + private File jarFile; + private CompletableFuture downloadFuture; + private VersionMetadata currentMetadata; private VersionMetadata latestMetadata; private long checkCacheExpires; @@ -71,6 +80,11 @@ public class Updater extends BukkitRunnable implements Listener { } } + try { + jarFile = new File(plugin.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + } catch (URISyntaxException e) { } + updateDownloader = new UpdateDownloader(); + if (updaterConfig.notify) { this.runTaskTimerAsynchronously(plugin, 0, 216000); // 3h plugin.getServer().getPluginManager().registerEvents(this, plugin); @@ -80,11 +94,33 @@ public class Updater extends BukkitRunnable implements Listener { DebugLogger.info("updater loaded", 1); } - public boolean installUpdate() { + /** + * download latest update + * @return null if no update else a future with the file (it can error with ioexcepton or signatureexception if signanture invalid) + */ + public CompletableFuture downloadUpdate() { if (latestMetadata == null) - return false; + return null; + + String url = metadataRetriever.getFileUrl(latestMetadata.label, latestMetadata.fileUrl); + downloadFuture = updateDownloader.downloadAndVerify(url, latestMetadata.sha256); + + return downloadFuture; + } + + public boolean installUpdate() { + File downloadedFile = null; + try { + downloadedFile = downloadFuture.join(); + // TODO what if we changed File to Path and every ref in this file + Files.move(downloadedFile.toPath(), jarFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (CompletionException | IOException e) { + e.printStackTrace(); + return false; + } finally { + downloadFuture = null; + } - // TODO dont forget about verifictaion return true; } diff --git a/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java b/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java index 5322cbe..d8c39ec 100644 --- a/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java +++ b/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java @@ -26,9 +26,9 @@ public class VersionMetadata { public String label; /** - * filename of the jar file in the version directory + * url of the downloadable jar file */ - public String file; + public String fileUrl; public String sha256;