diff --git a/pom.xml b/pom.xml index 6f3bbb7..7e6b8d8 100644 --- a/pom.xml +++ b/pom.xml @@ -11,6 +11,10 @@ testkey 123456 + + + scm:git:git@git.724.rocks:Minecon724/realweather.git + @@ -45,6 +49,11 @@ + + org.apache.maven.plugins + maven-release-plugin + 3.0.1 + org.apache.maven.plugins maven-shade-plugin diff --git a/release.properties b/release.properties new file mode 100644 index 0000000..30b640f --- /dev/null +++ b/release.properties @@ -0,0 +1,21 @@ +#release configuration +#Tue Jun 18 13:22:44 CEST 2024 +completedPhase=check-poms +exec.pomFileName=pom.xml +exec.snapshotReleasePluginAllowed=false +pinExternals=false +preparationGoals=clean verify +project.scm.eu.m724\:realweather.developerConnection=scm\:git\:git@git.724.rocks\:Minecon724/realweather.git +project.scm.eu.m724\:realweather.tag=HEAD +projectVersionPolicyConfig=${projectVersionPolicyConfig}\n +projectVersionPolicyId=default +pushChanges=true +releaseStrategyId=default +remoteTagging=true +scm.branchCommitComment=@{prefix} prepare branch @{releaseLabel} +scm.commentPrefix=[maven-release-plugin] +scm.developmentCommitComment=@{prefix} prepare for next development iteration +scm.releaseCommitComment=@{prefix} prepare release @{releaseLabel} +scm.rollbackCommitComment=@{prefix} rollback the release of @{releaseLabel} +scm.tagNameFormat=@{project.artifactId}-@{project.version} +scm.url=scm\:git\:git@git.724.rocks\:Minecon724/realweather.git diff --git a/src/main/java/eu/m724/realweather/DebugLogger.java b/src/main/java/eu/m724/realweather/DebugLogger.java index 5d2253f..6850958 100644 --- a/src/main/java/eu/m724/realweather/DebugLogger.java +++ b/src/main/java/eu/m724/realweather/DebugLogger.java @@ -13,6 +13,6 @@ public class DebugLogger { public static void info(String message, int minDebugLevel, Object... format) { if (debugLevel >= minDebugLevel) - baseLogger.info(String.format(message, format)); + baseLogger.info(message.formatted(format)); } } diff --git a/src/main/java/eu/m724/realweather/GlobalConstants.java b/src/main/java/eu/m724/realweather/GlobalConstants.java index 649bb18..14ad670 100644 --- a/src/main/java/eu/m724/realweather/GlobalConstants.java +++ b/src/main/java/eu/m724/realweather/GlobalConstants.java @@ -7,6 +7,7 @@ import eu.m724.realweather.mapper.MapperConfig; import eu.m724.realweather.thunder.ThunderConfig; import eu.m724.realweather.thunder.ThunderMaster; import eu.m724.realweather.time.TimeConfig; +import eu.m724.realweather.updater.UpdaterConfig; import eu.m724.realweather.weather.PlayerWeatherDirectory; import eu.m724.realweather.weather.WeatherConfig; @@ -15,6 +16,7 @@ public class GlobalConstants { static TimeConfig timeConfig; static ThunderConfig thunderConfig; static MapperConfig mapperConfig; + static UpdaterConfig updaterConfig; static ThunderMaster thunderMaster; @@ -34,6 +36,9 @@ public class GlobalConstants { public static MapperConfig getMapperConfig() { return mapperConfig; } + public static UpdaterConfig getUpdaterConfig() { + return updaterConfig; + } public static ThunderMaster getThunderMaster() { return thunderMaster; } diff --git a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java index edefab3..317e947 100644 --- a/src/main/java/eu/m724/realweather/RealWeatherPlugin.java +++ b/src/main/java/eu/m724/realweather/RealWeatherPlugin.java @@ -14,6 +14,7 @@ import com.google.common.base.Charsets; import eu.m724.realweather.commands.AdminCommand; import eu.m724.realweather.commands.GeoCommand; import eu.m724.realweather.commands.LocalTimeCommand; +import eu.m724.realweather.commands.UpdateCommand; import eu.m724.realweather.mapper.Mapper; import eu.m724.realweather.mapper.MapperConfig; import eu.m724.realweather.object.UserException; @@ -23,6 +24,7 @@ import eu.m724.realweather.time.TimeConfig; import eu.m724.realweather.time.TimeMaster; import eu.m724.realweather.updater.SignatureValidator; import eu.m724.realweather.updater.Updater; +import eu.m724.realweather.updater.UpdaterConfig; import eu.m724.realweather.weather.WeatherConfig; import eu.m724.realweather.weather.WeatherMaster; import eu.m724.wtapi.provider.exception.ProviderException; @@ -31,6 +33,7 @@ public class RealWeatherPlugin extends JavaPlugin { private WeatherMaster weatherMaster; private ThunderMaster thunderMaster; private TimeMaster timeMaster; + private Updater updater; private Logger logger; @@ -112,6 +115,10 @@ public class RealWeatherPlugin extends JavaPlugin { GlobalConstants.timeConfig = TimeConfig.fromConfiguration(timeConfiguration); timeMaster = new TimeMaster(GlobalConstants.timeConfig); timeMaster.init(); + + GlobalConstants.updaterConfig = UpdaterConfig.fromConfiguration(configuration.getConfigurationSection("updater")); + updater = new Updater(GlobalConstants.updaterConfig); + updater.init(); } catch (UserException e) { logger.severe("There are errors in your config:"); logger.severe(e.getMessage()); @@ -132,13 +139,12 @@ public class RealWeatherPlugin extends JavaPlugin { GlobalConstants.thunderMaster = thunderMaster; getCommand("rwadmin").setExecutor(new AdminCommand()); + getCommand("rwadmin update").setExecutor(new UpdateCommand(updater)); getCommand("geo").setExecutor(new GeoCommand()); if (GlobalConstants.timeConfig.enabled) getCommand("localtime").setExecutor(new LocalTimeCommand()); - new Updater().init(); - DebugLogger.info("ended loading", 1); } diff --git a/src/main/java/eu/m724/realweather/commands/UpdateCommand.java b/src/main/java/eu/m724/realweather/commands/UpdateCommand.java new file mode 100644 index 0000000..bc16fc8 --- /dev/null +++ b/src/main/java/eu/m724/realweather/commands/UpdateCommand.java @@ -0,0 +1,37 @@ +package eu.m724.realweather.commands; + +import java.util.concurrent.CompletableFuture; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; + +import eu.m724.realweather.updater.Updater; +import eu.m724.realweather.updater.metadata.VersionMetadata; + +public class UpdateCommand implements CommandExecutor { + private Updater updater; + + public UpdateCommand(Updater updater) { + this.updater = updater; + } + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + 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 + } + }); + + return true; + } + +} diff --git a/src/main/java/eu/m724/realweather/updater/MetadataRetriever.java b/src/main/java/eu/m724/realweather/updater/MetadataRetriever.java deleted file mode 100644 index 08ec9d3..0000000 --- a/src/main/java/eu/m724/realweather/updater/MetadataRetriever.java +++ /dev/null @@ -1,62 +0,0 @@ -package eu.m724.realweather.updater; - -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.concurrent.CompletableFuture; - -import org.bukkit.plugin.Plugin; - -import com.google.gson.Gson; - -public class MetadataRetriever { - private String version; - - private CompletableFuture currentMetadataCached = null; - - public MetadataRetriever(Plugin plugin) { - this.version = plugin.getDescription().getVersion(); - } - - private CompletableFuture getMetadataFromUrl(String url) { - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(url)) - .header("User-Agent", "rwu/1") - .build(); - - CompletableFuture> responseFuture = - HttpClient.newBuilder() - .followRedirects(Redirect.NORMAL) - .proxy(ProxySelector.getDefault()).build(). - sendAsync(request, BodyHandlers.ofString()); - - CompletableFuture metadataFuture = - responseFuture.thenApply(response -> { - if (response.statusCode() != 200) - return null; - - VersionMetadata versionMetadata = new Gson().fromJson(response.body(), VersionMetadata.class); - return versionMetadata; - }); - - return metadataFuture; - } - - public CompletableFuture getLatestVersionMetadata() { - return getMetadataFromUrl("https://git.724.rocks/Minecon724/realweather-metadata/raw/branch/master/latest/latest-v1.json"); - } - - public CompletableFuture getCurrentVersionMetadata() { - if (currentMetadataCached != null) - currentMetadataCached = getVersionMetadata(version); - return currentMetadataCached; - } - - public CompletableFuture getVersionMetadata(String version) { - return getMetadataFromUrl("https://git.724.rocks/Minecon724/realweather-metadata/raw/branch/master/releases/" + version + "/meta-v1.json"); - } -} diff --git a/src/main/java/eu/m724/realweather/updater/Updater.java b/src/main/java/eu/m724/realweather/updater/Updater.java index 0757312..8a5d1c3 100644 --- a/src/main/java/eu/m724/realweather/updater/Updater.java +++ b/src/main/java/eu/m724/realweather/updater/Updater.java @@ -1,8 +1,8 @@ package eu.m724.realweather.updater; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -10,18 +10,25 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.plugin.Plugin; import org.bukkit.scheduler.BukkitRunnable; - import eu.m724.realweather.DebugLogger; import eu.m724.realweather.GlobalConstants; +import eu.m724.realweather.object.UserException; +import eu.m724.realweather.updater.metadata.MetadataRetriever; +import eu.m724.realweather.updater.metadata.MetadataServerException; +import eu.m724.realweather.updater.metadata.VersionMetadata; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; import net.md_5.bungee.api.chat.ComponentBuilder; public class Updater extends BukkitRunnable implements Listener { - private Plugin plugin = GlobalConstants.getPlugin(); - private MetadataRetriever metadataRetriever = new MetadataRetriever(plugin); + private UpdaterConfig updaterConfig; + private MetadataRetriever metadataRetriever; - private VersionMetadata availableUpdate = null; + private Plugin plugin = GlobalConstants.getPlugin(); + + private VersionMetadata currentMetadata; + private VersionMetadata latestMetadata; + private long checkCacheExpires; private BaseComponent updateMessage = null; private BaseComponent updateActionMessage = @@ -29,19 +36,91 @@ public class Updater extends BukkitRunnable implements Listener { .append("/rwadmin update").color(ChatColor.AQUA) .build(); - public void init() { - this.runTaskTimerAsynchronously(plugin, 0, 216000); // 3h - plugin.getServer().getPluginManager().registerEvents(this, plugin); + public Updater(UpdaterConfig updaterConfig) { + this.updaterConfig = updaterConfig; + this.metadataRetriever = new MetadataRetriever(plugin, updaterConfig.channel); + } + + public void init() throws UserException { + try { + DebugLogger.info("probing for channels...", 1); + + List channels = metadataRetriever.getChannels().join(); + if (!channels.contains(updaterConfig.channel)) { + throw new UserException( + "Invalid channel: %s. Valid ones are: %s" + .formatted(updaterConfig.channel, String.join(", ", channels))); // WHY DID NOBODY TELL ME ABOUT .formatted + } + + currentMetadata = metadataRetriever.getCurrentVersionMetadata().join(); + DebugLogger.info("current: %d", 1, currentMetadata.id); + } catch (CompletionException e) { + int statusCode = ((MetadataServerException) e.getCause()).getStatusCode(); + if (statusCode >= 500) { + DebugLogger.info("Unable to contact the update server! %d", 0, statusCode); + DebugLogger.info("As it's a server error, it's probably temporary and not a configuration issue, so proceeding.", 0); + } else { + DebugLogger.info("Update server returned unexpected status code! %d", 0, statusCode); + if (plugin.getDescription().getVersion().endsWith("SNAPSHOT")) { + DebugLogger.info("It looks like this is a development snapshot, possibly built from source. So actually this is expected.", 0); + } else { + DebugLogger.info("This is probably critical. RealWeather will continue without updater.", 0); + DebugLogger.info("Try restarting the server. If that doesn't help, contact support.", 0); + } + return; + } + } + + if (updaterConfig.notify) { + this.runTaskTimerAsynchronously(plugin, 0, 216000); // 3h + plugin.getServer().getPluginManager().registerEvents(this, plugin); + DebugLogger.info("updater will notify", 1); + } + + DebugLogger.info("updater loaded", 1); } public boolean installUpdate() { - if (availableUpdate == null) + if (latestMetadata == null) return false; // TODO dont forget about verifictaion return true; } + public CompletableFuture getLatestVersion() { + if (System.currentTimeMillis() < checkCacheExpires) + return CompletableFuture.completedFuture(latestMetadata); + else + return checkForNewVersion().handle((result, ex) -> null); + } + + /** + * this can throw completionexception + * @return + */ + private CompletableFuture checkForNewVersion() { + CompletableFuture latestMetadataFuture = + metadataRetriever.getLatestVersionMetadata(); + + CompletableFuture newMetadataFuture = + latestMetadataFuture.thenApply(latestMetadata -> { + DebugLogger.info("Current version: %s (%d)", 2, currentMetadata.label, currentMetadata.id); + DebugLogger.info("Latest version: %s (%d)", 2, latestMetadata.label, latestMetadata.id); + + if (currentMetadata.id < latestMetadata.id) { + this.latestMetadata = latestMetadata; + this.checkCacheExpires = System.currentTimeMillis() + 10800000; + return latestMetadata; + } + + this.checkCacheExpires = System.currentTimeMillis() + 3600000; + return null; + }); + + return newMetadataFuture; + } + @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); @@ -57,30 +136,22 @@ public class Updater extends BukkitRunnable implements Listener { @Override public void run() { DebugLogger.info("Checking for update...", 2); - CompletableFuture currentMetadataFuture = - metadataRetriever.getCurrentVersionMetadata(); - - CompletableFuture latestMetadataFuture = - metadataRetriever.getLatestVersionMetadata(); - VersionMetadata currentMetadata = currentMetadataFuture.join(); - VersionMetadata latestMetadata = latestMetadataFuture.join(); + CompletableFuture newVersionFuture = checkForNewVersion(); - DebugLogger.info("Current version: %s (%d)", 2, currentMetadata.label, currentMetadata.id); - DebugLogger.info("Latest version: %s (%d)", 2, latestMetadata.label, latestMetadata.id); - - if (currentMetadata.id >= latestMetadata.id) return; - - availableUpdate = latestMetadata; - - String formattedDate = new SimpleDateFormat("dd.MM").format(new Date(latestMetadata.timestamp)); + try { + newVersionFuture.join(); + } catch (CompletionException e) { + int statusCode = ((MetadataServerException) e.getCause()).getStatusCode(); + DebugLogger.info("Couldn't check for updates: %d", 0, statusCode); + } updateMessage = new ComponentBuilder("An update is available!\n") .color(ChatColor.YELLOW) .append("RealWeather ").color(ChatColor.GOLD) .append(latestMetadata.label).color(ChatColor.AQUA).bold(true) .append(" released ").color(ChatColor.GOLD) - .append(formattedDate).color(ChatColor.AQUA) + .append(latestMetadata.getFormattedDate()).color(ChatColor.AQUA) .append("\nCurrent: ").color(ChatColor.GRAY) .append(currentMetadata.label).color(ChatColor.DARK_AQUA) .build(); diff --git a/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java b/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java new file mode 100644 index 0000000..162c629 --- /dev/null +++ b/src/main/java/eu/m724/realweather/updater/UpdaterConfig.java @@ -0,0 +1,17 @@ +package eu.m724.realweather.updater; + +import org.bukkit.configuration.ConfigurationSection; + +public class UpdaterConfig { + public boolean notify; + public String channel; + + public static UpdaterConfig fromConfiguration(ConfigurationSection configuration) { + UpdaterConfig updaterConfig = new UpdaterConfig(); + + updaterConfig.notify = configuration.getBoolean("notify"); + updaterConfig.channel = configuration.getString("channel"); + + return updaterConfig; + } +} diff --git a/src/main/java/eu/m724/realweather/updater/metadata/MetadataRetriever.java b/src/main/java/eu/m724/realweather/updater/metadata/MetadataRetriever.java new file mode 100644 index 0000000..192c05d --- /dev/null +++ b/src/main/java/eu/m724/realweather/updater/metadata/MetadataRetriever.java @@ -0,0 +1,107 @@ +package eu.m724.realweather.updater.metadata; + +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 org.bukkit.plugin.Plugin; + +import com.google.gson.Gson; + +public class MetadataRetriever { + private String version; + private String channel; + + private CompletableFuture currentMetadataCached = null; + + public MetadataRetriever(Plugin plugin, String channel) { + this.version = plugin.getDescription().getVersion(); + this.channel = channel; + } + + /** + * the completablefuture can throw a completionexception with {@link eu.m724.realweather.updater.metadata.MetadataServerException} + */ + private CompletableFuture getMetadataOf(String version) { + String url = String.format( + "https://git.724.rocks/Minecon724/realweather-metadata/raw/branch/master/data/%s/%s/%s", + channel, version, "meta-v1.json"); + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("User-Agent", "rwu/1") // real weather updater v1 + .build(); + + CompletableFuture> responseFuture = + HttpClient.newBuilder() + .followRedirects(Redirect.NORMAL) + .proxy(ProxySelector.getDefault()).build(). + sendAsync(request, BodyHandlers.ofString()); + + CompletableFuture metadataFuture = + responseFuture.thenApply(response -> { + if (response.statusCode() != 200) + throw new CompletionException(new MetadataServerException(response.statusCode())); + + VersionMetadata versionMetadata = new Gson().fromJson(response.body(), VersionMetadata.class); + return versionMetadata; + }); + + return metadataFuture; + } + + public CompletableFuture> getChannels() { + String url = "https://git.724.rocks/Minecon724/realweather-metadata/raw/branch/master/data/channels.txt"; + + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(url)) + .header("User-Agent", "rwu/1") // real weather updater v1 + .build(); + + CompletableFuture> responseFuture = + HttpClient.newBuilder() + .followRedirects(Redirect.NORMAL) + .proxy(ProxySelector.getDefault()).build(). + sendAsync(request, BodyHandlers.ofString()); + + CompletableFuture> channelsFuture = + responseFuture.thenApply(response -> { + if (response.statusCode() != 200) + throw new CompletionException(new MetadataServerException(response.statusCode())); + + return response.body().lines().toList(); + }); + + return channelsFuture; // TODO remove repeated code? + } + + /** + * the {@link CompletableFuture} can throw a {@link CompletionException} with {@link MetadataServerException} + */ + public CompletableFuture getLatestVersionMetadata() { + return getVersionMetadata("latest"); + } + + /** + * the completablefuture can throw a completionexception with {@link MetadataServerException} + */ + public CompletableFuture getCurrentVersionMetadata() { + if (currentMetadataCached == null) + currentMetadataCached = getVersionMetadata(version); + return currentMetadataCached; // TODO reconsider this + } + + /** + * the completablefuture can throw a completionexception with {@link MetadataServerException} + */ + public CompletableFuture getVersionMetadata(String version) { + return getMetadataOf(version); // TODO remove this and rename that function? + } +} diff --git a/src/main/java/eu/m724/realweather/updater/metadata/MetadataServerException.java b/src/main/java/eu/m724/realweather/updater/metadata/MetadataServerException.java new file mode 100644 index 0000000..881cd92 --- /dev/null +++ b/src/main/java/eu/m724/realweather/updater/metadata/MetadataServerException.java @@ -0,0 +1,16 @@ +package eu.m724.realweather.updater.metadata; + +public class MetadataServerException extends Exception { + private int statusCode; + + private static final long serialVersionUID = -782470406494196579L; + + public MetadataServerException(int statusCode) { + this.statusCode = statusCode; + } + + public int getStatusCode() { + return statusCode; + } + +} diff --git a/src/main/java/eu/m724/realweather/updater/VersionMetadata.java b/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java similarity index 62% rename from src/main/java/eu/m724/realweather/updater/VersionMetadata.java rename to src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java index f020f06..5322cbe 100644 --- a/src/main/java/eu/m724/realweather/updater/VersionMetadata.java +++ b/src/main/java/eu/m724/realweather/updater/metadata/VersionMetadata.java @@ -1,4 +1,7 @@ -package eu.m724.realweather.updater; +package eu.m724.realweather.updater.metadata; + +import java.text.SimpleDateFormat; +import java.util.Date; public class VersionMetadata { /** @@ -26,4 +29,10 @@ public class VersionMetadata { * filename of the jar file in the version directory */ public String file; + + public String sha256; + + public String getFormattedDate() { + return new SimpleDateFormat("dd.MM").format(new Date(timestamp)); + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 96d889f..7069086 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -6,9 +6,14 @@ enabled: true updater: - # notify players about plugin updates - # revelant permission node: realweather.update.notify + # Notify players and console about plugin updates + # This also controls automatic checking + # You can still update with /rwadmin update + # Revelant permission node: realweather.update.notify notify: true + # stable for stable releases + # testing for latest builds (untested hence the name) + channel: testing # 0 - no debug # 1 - debug loading modules diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a0905ba..efad6c8 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -16,12 +16,15 @@ commands: description: RealWeather admin command permission: realweather.admin permission-message: You do not have permission to use this command. - # usage is processed in code + rwadmin update: + description: Update RealWeather + permission: realweather.admin.update + permission-message: You do not have permission to use this command. + geo: description: Convert lat,lon to x,y,z and vice versa permission: realweather.command.geo permission-message: You do not have permission to use this command. - # usage is processed in code localtime: description: Get real time in current location permission: realweather.command.localtime @@ -32,6 +35,8 @@ permissions: realweather.admin: description: Allows admin management with /rwadmin + realweather.admin.update: + description: Allows installing updates with /rwadmin update realweather.command.geo: description: Allows /geo