new updater
This commit is contained in:
parent
68bbd9b9a5
commit
1fab21601e
13 changed files with 156 additions and 537 deletions
5
pom.xml
5
pom.xml
|
@ -51,6 +51,11 @@
|
||||||
<artifactId>wtapi</artifactId>
|
<artifactId>wtapi</artifactId>
|
||||||
<version>0.7</version>
|
<version>0.7</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>eu.m724</groupId>
|
||||||
|
<artifactId>jarupdater</artifactId>
|
||||||
|
<version>0.1.0</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -21,8 +21,7 @@ import eu.m724.realweather.thunder.ThunderConfig;
|
||||||
import eu.m724.realweather.thunder.ThunderMaster;
|
import eu.m724.realweather.thunder.ThunderMaster;
|
||||||
import eu.m724.realweather.time.TimeConfig;
|
import eu.m724.realweather.time.TimeConfig;
|
||||||
import eu.m724.realweather.time.TimeMaster;
|
import eu.m724.realweather.time.TimeMaster;
|
||||||
import eu.m724.realweather.updater.SignatureValidator;
|
import eu.m724.realweather.updater.PluginUpdater;
|
||||||
import eu.m724.realweather.updater.Updater;
|
|
||||||
import eu.m724.realweather.updater.UpdaterConfig;
|
import eu.m724.realweather.updater.UpdaterConfig;
|
||||||
import eu.m724.realweather.weather.PlayerWeatherDirectory;
|
import eu.m724.realweather.weather.PlayerWeatherDirectory;
|
||||||
import eu.m724.realweather.weather.WeatherConfig;
|
import eu.m724.realweather.weather.WeatherConfig;
|
||||||
|
@ -34,7 +33,7 @@ public class RealWeatherPlugin extends JavaPlugin {
|
||||||
private WeatherMaster weatherMaster;
|
private WeatherMaster weatherMaster;
|
||||||
private ThunderMaster thunderMaster;
|
private ThunderMaster thunderMaster;
|
||||||
private TimeMaster timeMaster;
|
private TimeMaster timeMaster;
|
||||||
private Updater updater;
|
private PluginUpdater updater;
|
||||||
|
|
||||||
private Logger logger;
|
private Logger logger;
|
||||||
|
|
||||||
|
@ -42,17 +41,6 @@ public class RealWeatherPlugin extends JavaPlugin {
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
logger = getLogger();
|
logger = getLogger();
|
||||||
|
|
||||||
// TODO remove these lines
|
|
||||||
SignatureValidator signatureValidator = new SignatureValidator(this);
|
|
||||||
logger.info("Signature of this JAR: " + signatureValidator.getCertificate().getSubjectX500Principal().getName());
|
|
||||||
|
|
||||||
if (!signatureValidator.isValid()) {
|
|
||||||
logger.severe("Key is not valid");
|
|
||||||
getServer().getPluginManager().disablePlugin(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// TODO remove those lines
|
|
||||||
|
|
||||||
File dataFolder = getDataFolder();
|
File dataFolder = getDataFolder();
|
||||||
File modulesFolder = new File("modules");
|
File modulesFolder = new File("modules");
|
||||||
modulesFolder.mkdir();
|
modulesFolder.mkdir();
|
||||||
|
@ -119,7 +107,7 @@ public class RealWeatherPlugin extends JavaPlugin {
|
||||||
timeMaster.init();
|
timeMaster.init();
|
||||||
|
|
||||||
GlobalConstants.updaterConfig = UpdaterConfig.fromConfiguration(configuration.getConfigurationSection("updater"));
|
GlobalConstants.updaterConfig = UpdaterConfig.fromConfiguration(configuration.getConfigurationSection("updater"));
|
||||||
updater = new Updater(GlobalConstants.updaterConfig);
|
updater = PluginUpdater.build(this, GlobalConstants.updaterConfig);
|
||||||
updater.init();
|
updater.init();
|
||||||
} catch (UserError | NoSuchProviderException e) {
|
} catch (UserError | NoSuchProviderException e) {
|
||||||
logger.severe("There are errors in your config:");
|
logger.severe("There are errors in your config:");
|
||||||
|
|
|
@ -12,7 +12,7 @@ import eu.m724.realweather.mapper.MapperConfig;
|
||||||
import eu.m724.realweather.thunder.ThunderConfig;
|
import eu.m724.realweather.thunder.ThunderConfig;
|
||||||
import eu.m724.realweather.thunder.ThunderMaster;
|
import eu.m724.realweather.thunder.ThunderMaster;
|
||||||
import eu.m724.realweather.time.TimeConfig;
|
import eu.m724.realweather.time.TimeConfig;
|
||||||
import eu.m724.realweather.updater.Updater;
|
import eu.m724.realweather.updater.PluginUpdater;
|
||||||
import eu.m724.realweather.weather.WeatherConfig;
|
import eu.m724.realweather.weather.WeatherConfig;
|
||||||
import net.md_5.bungee.api.ChatColor;
|
import net.md_5.bungee.api.ChatColor;
|
||||||
import net.md_5.bungee.api.chat.BaseComponent;
|
import net.md_5.bungee.api.chat.BaseComponent;
|
||||||
|
@ -33,7 +33,7 @@ public class AdminCommand implements CommandExecutor {
|
||||||
private BaseComponent enabledComponent = TextComponent.fromLegacy("YES\n", ChatColor.GREEN);
|
private BaseComponent enabledComponent = TextComponent.fromLegacy("YES\n", ChatColor.GREEN);
|
||||||
private BaseComponent disabledComponent = TextComponent.fromLegacy("NO\n", ChatColor.RED);
|
private BaseComponent disabledComponent = TextComponent.fromLegacy("NO\n", ChatColor.RED);
|
||||||
|
|
||||||
public AdminCommand(Updater updater) {
|
public AdminCommand(PluginUpdater updater) {
|
||||||
this.updateCommand = new UpdateCommand(updater);
|
this.updateCommand = new UpdateCommand(updater);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package eu.m724.realweather.commands;
|
package eu.m724.realweather.commands;
|
||||||
|
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
|
|
||||||
import eu.m724.realweather.updater.Updater;
|
import eu.m724.jarupdater.Updater;
|
||||||
import eu.m724.realweather.updater.metadata.VersionMetadata;
|
import eu.m724.jarupdater.object.Version;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* not actually a command but deserves a separate file
|
* not actually a command but deserves a separate file
|
||||||
|
@ -22,13 +25,13 @@ public class UpdateCommand {
|
||||||
if (!sender.hasPermission("realweather.admin.update")) return false;
|
if (!sender.hasPermission("realweather.admin.update")) return false;
|
||||||
|
|
||||||
sender.sendMessage("Please wait");
|
sender.sendMessage("Please wait");
|
||||||
CompletableFuture<VersionMetadata> latestFuture = updater.getLatestVersion();
|
CompletableFuture<Version> latestFuture = updater.getLatestVersion();
|
||||||
|
|
||||||
if (args.length == 0) {
|
if (args.length == 0) {
|
||||||
latestFuture.thenAccept(metadata -> {
|
latestFuture.thenAccept(metadata -> {
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
sender.sendMessage("An update is available!");
|
sender.sendMessage("An update is available!");
|
||||||
sender.sendMessage("RealWeather %s released %s".formatted(metadata.label, metadata.getFormattedDate()));
|
sender.sendMessage("RealWeather %s released %s".formatted(metadata.getLabel(), formatDate(metadata.getTimestamp())));
|
||||||
sender.sendMessage("To download: /rwadmin update download");
|
sender.sendMessage("To download: /rwadmin update download");
|
||||||
} else {
|
} else {
|
||||||
sender.sendMessage("No new updates"); // TODO color
|
sender.sendMessage("No new updates"); // TODO color
|
||||||
|
@ -40,7 +43,7 @@ public class UpdateCommand {
|
||||||
if (action.equals("download")) {
|
if (action.equals("download")) {
|
||||||
sender.sendMessage("Started download");
|
sender.sendMessage("Started download");
|
||||||
|
|
||||||
updater.downloadUpdate().handle((file, ex) -> {
|
updater.downloadLatestVersion().handle((file, ex) -> {
|
||||||
sender.sendMessage("Download failed. See console for details.");
|
sender.sendMessage("Download failed. See console for details.");
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
return null;
|
return null;
|
||||||
|
@ -50,14 +53,25 @@ public class UpdateCommand {
|
||||||
});
|
});
|
||||||
|
|
||||||
} else if (action.equals("install")) {
|
} else if (action.equals("install")) {
|
||||||
if (updater.installUpdate())
|
try {
|
||||||
sender.sendMessage("Update installed, restart server to apply.");
|
updater.installLatestVersion().handle((v, ex) -> {
|
||||||
else sender.sendMessage("Update not installed, see console.");
|
sender.sendMessage("Install failed. See console for details.");
|
||||||
|
ex.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}).thenAccept(v -> {
|
||||||
|
sender.sendMessage("Installation completed, restart server to apply");
|
||||||
|
});
|
||||||
|
} catch (NoSuchFileException e) {
|
||||||
|
sender.sendMessage("Download the update first");
|
||||||
|
}
|
||||||
} else return false;
|
} else return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String formatDate(long timestamp) { // TODO move this
|
||||||
|
return DateTimeFormatter.ofPattern("dd.MM.yyyy").format(Instant.ofEpochSecond(timestamp));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.m724.realweather.updater;
|
||||||
|
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import eu.m724.jarupdater.environment.ConstantEnvironment;
|
||||||
|
import eu.m724.realweather.GlobalConstants;
|
||||||
|
|
||||||
|
public class PluginEnvironment extends ConstantEnvironment {
|
||||||
|
|
||||||
|
public PluginEnvironment(Plugin plugin) {
|
||||||
|
super(plugin.getDescription().getVersion(),
|
||||||
|
GlobalConstants.getUpdaterConfig().channel,
|
||||||
|
Path.of(plugin.getClass().getProtectionDomain().getCodeSource().getLocation().getPath()));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
37
src/main/java/eu/m724/realweather/updater/PluginUpdater.java
Normal file
37
src/main/java/eu/m724/realweather/updater/PluginUpdater.java
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package eu.m724.realweather.updater;
|
||||||
|
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import eu.m724.jarupdater.Updater;
|
||||||
|
import eu.m724.jarupdater.download.Downloader;
|
||||||
|
import eu.m724.jarupdater.download.SimpleDownloader;
|
||||||
|
import eu.m724.jarupdater.environment.Environment;
|
||||||
|
import eu.m724.jarupdater.live.GiteaMetadataDAO;
|
||||||
|
import eu.m724.jarupdater.live.MetadataDAO;
|
||||||
|
import eu.m724.jarupdater.live.MetadataFacade;
|
||||||
|
|
||||||
|
public class PluginUpdater extends Updater {
|
||||||
|
private UpdaterConfig updaterConfig;
|
||||||
|
|
||||||
|
PluginUpdater(Environment environment, MetadataFacade metadataProvider, Downloader downloader, UpdaterConfig updaterConfig) {
|
||||||
|
super(environment, metadataProvider, downloader);
|
||||||
|
this.updaterConfig = updaterConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static PluginUpdater build(Plugin plugin, UpdaterConfig updaterConfig) {
|
||||||
|
Environment environment = new PluginEnvironment(plugin);
|
||||||
|
MetadataDAO metadataDAO = new GiteaMetadataDAO("https://git.m724.eu/Minecon724/realweather", "master");
|
||||||
|
MetadataFacade metadataFacade = new MetadataFacade(environment, metadataDAO);
|
||||||
|
Downloader downloader = new SimpleDownloader("realweather");
|
||||||
|
|
||||||
|
return new PluginUpdater(environment, metadataFacade, downloader, updaterConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void init() {
|
||||||
|
if (!updaterConfig.notify) return;
|
||||||
|
|
||||||
|
UpdateNotifier updateNotifier = new UpdateNotifier(this, (version) -> {});
|
||||||
|
updateNotifier.register();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,40 +0,0 @@
|
||||||
package eu.m724.realweather.updater;
|
|
||||||
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
import java.security.spec.X509EncodedKeySpec;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import eu.m724.realweather.RealWeatherPlugin;
|
|
||||||
|
|
||||||
// TODO rework this for updater
|
|
||||||
public class SignatureValidator {
|
|
||||||
public RealWeatherPlugin plugin;
|
|
||||||
public static final String encodedPublicKey = "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAptv/9qIJXrs/4T1MOkY1QPU/TuLsyCsJdoA2PIO1qdS3nRJBPgkRf2sK6nG1VhOaUHLXoj8lZtQQLcY76CNLqFGFmimo7RDnJjHpxHUzI9pKZJOQ9sEVCDFtoLQiit23t6MAO7GBjJXMNFLonxyay6pTABJo3VYyjg2bE4kd1wjg73RPMQY+zykaRQBUE167PAVkmuYxJK680EYmZph9kQTS12autU2qGFTvsPbmmdhtF7Xy8u84CtEucgRT9HSh0y8MuC0esMGhZtB9gsWcGET763DHtArEMekBnjByb3k+gGiG0Y1K9ygBn+nNVKP66KJGCWFuno8xy+LNiZKX4pUnrJcTyLvZg7PvjdZTye54PKkAAOACAbcFBiat38Zes5ZOKZIBEjC2IXbhfySoOn5WAk+XPsm3gVlgO9d51iOVDDBx5MCqq802lOyIGog1BlbhnGZ2+cSvFo7ZWpF0f93uG5UKBqRF+Q9cPA36SMUAoQ2DWFEZOYXwFgCXxVvFAgMBAAE=";
|
|
||||||
|
|
||||||
public SignatureValidator(RealWeatherPlugin plugin) {
|
|
||||||
this.plugin = plugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public X509Certificate getCertificate() {
|
|
||||||
return (X509Certificate) plugin.getClass().getProtectionDomain().getCodeSource().getCertificates()[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isValid() {
|
|
||||||
PublicKey currentPublicKey = getCertificate().getPublicKey();
|
|
||||||
PublicKey expectedPublicKey = null;
|
|
||||||
|
|
||||||
try {
|
|
||||||
X509EncodedKeySpec spec = new X509EncodedKeySpec(Base64.getDecoder().decode(encodedPublicKey));
|
|
||||||
expectedPublicKey = KeyFactory.getInstance("RSA").generatePublic(spec);
|
|
||||||
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
|
|
||||||
// TODO Auto-generated catch block
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
return expectedPublicKey.equals(currentPublicKey);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,109 +0,0 @@
|
||||||
package eu.m724.realweather.updater;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
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.nio.file.Files;
|
|
||||||
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;
|
|
||||||
|
|
||||||
public class UpdateDownloader {
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param url
|
|
||||||
* @param sha256hex
|
|
||||||
* @return
|
|
||||||
* @throws SignatureException if signature doesnt match
|
|
||||||
*/
|
|
||||||
public CompletableFuture<File> 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<File> download(String url) { // TODO progress?
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(url))
|
|
||||||
.header("User-Agent", "rwu/1") // real weather updater v1
|
|
||||||
.build();
|
|
||||||
|
|
||||||
CompletableFuture<HttpResponse<InputStream>> responseFuture =
|
|
||||||
HttpClient.newBuilder()
|
|
||||||
.followRedirects(Redirect.NORMAL)
|
|
||||||
.proxy(ProxySelector.getDefault()).build().
|
|
||||||
sendAsync(request, BodyHandlers.ofInputStream());
|
|
||||||
|
|
||||||
CompletableFuture<File> fileFuture =
|
|
||||||
responseFuture.thenApply(response -> {
|
|
||||||
File downloadFile = null;
|
|
||||||
try {
|
|
||||||
InputStream bodyStream = response.body();
|
|
||||||
downloadFile = Files.createTempFile("realweather", null).toFile();
|
|
||||||
FileOutputStream fileStream = new FileOutputStream(downloadFile);
|
|
||||||
|
|
||||||
while (bodyStream.available() > 0) {
|
|
||||||
bodyStream.transferTo(fileStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
bodyStream.close();
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new CompletionException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return downloadFile;
|
|
||||||
});
|
|
||||||
|
|
||||||
return fileFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
private byte[] computeFileSha256Hash(File file) throws IOException {
|
|
||||||
MessageDigest digest = null;
|
|
||||||
try {
|
|
||||||
digest = MessageDigest.getInstance("SHA-256");
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
try (FileInputStream fileInputStream = new FileInputStream(file)) {
|
|
||||||
byte[] buffer = new byte[16384];
|
|
||||||
int len;
|
|
||||||
|
|
||||||
while ((len = fileInputStream.read(buffer)) != -1)
|
|
||||||
digest.update(buffer, 0, len);
|
|
||||||
}
|
|
||||||
|
|
||||||
return digest.digest();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean compareBytesHex(byte[] bytes, String hecks) {
|
|
||||||
return Arrays.equals(bytes, HexFormat.of().parseHex(hecks));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
package eu.m724.realweather.updater;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.event.player.PlayerJoinEvent;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
import org.bukkit.scheduler.BukkitRunnable;
|
||||||
|
|
||||||
|
import eu.m724.jarupdater.Updater;
|
||||||
|
import eu.m724.jarupdater.object.Version;
|
||||||
|
import eu.m724.realweather.DebugLogger;
|
||||||
|
import eu.m724.realweather.GlobalConstants;
|
||||||
|
|
||||||
|
public class UpdateNotifier extends BukkitRunnable implements Listener { // TODO move this to jarupdater
|
||||||
|
private Updater updater;
|
||||||
|
private Consumer<Version> updateConsumer;
|
||||||
|
|
||||||
|
private Plugin plugin = GlobalConstants.getPlugin();
|
||||||
|
private Version latestVersion;
|
||||||
|
|
||||||
|
public UpdateNotifier(Updater updater, Consumer<Version> updateConsumer) {
|
||||||
|
this.updater = updater;
|
||||||
|
this.updateConsumer = updateConsumer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void register() {
|
||||||
|
this.runTaskTimerAsynchronously(plugin, 0, 432000); // 6h
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
|
||||||
|
try {
|
||||||
|
latestVersion = updater.getLatestVersion().join();
|
||||||
|
} catch (CompletionException e) {
|
||||||
|
Throwable ex = e.getCause();
|
||||||
|
|
||||||
|
if (ex instanceof IOException)
|
||||||
|
DebugLogger.info("error trying to contact update server: %d", 0, ex.getMessage());
|
||||||
|
else e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestVersion == null) return;
|
||||||
|
|
||||||
|
for (Player player : plugin.getServer().getOnlinePlayers()) {
|
||||||
|
if (player.hasPermission("realweather.update.notify")) {
|
||||||
|
player.sendMessage("RealWeather is outdated. /rwadmin update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateConsumer.accept(latestVersion);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPlayerJoin(PlayerJoinEvent e) {
|
||||||
|
Player player = e.getPlayer();
|
||||||
|
if (latestVersion != null && player.hasPermission("realweather.update.notify")) {
|
||||||
|
player.sendMessage("RealWeather is outdated. /rwadmin update");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,198 +0,0 @@
|
||||||
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;
|
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.EventHandler;
|
|
||||||
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.exception.UserError;
|
|
||||||
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 UpdaterConfig updaterConfig;
|
|
||||||
private MetadataRetriever metadataRetriever;
|
|
||||||
private UpdateDownloader updateDownloader;
|
|
||||||
|
|
||||||
private Plugin plugin = GlobalConstants.getPlugin();
|
|
||||||
|
|
||||||
private File jarFile;
|
|
||||||
private CompletableFuture<File> downloadFuture;
|
|
||||||
|
|
||||||
private VersionMetadata currentMetadata;
|
|
||||||
private VersionMetadata latestMetadata;
|
|
||||||
private long checkCacheExpires;
|
|
||||||
|
|
||||||
private BaseComponent updateMessage = null;
|
|
||||||
private BaseComponent updateActionMessage =
|
|
||||||
new ComponentBuilder("To update: ").color(ChatColor.YELLOW)
|
|
||||||
.append("/rwadmin update").color(ChatColor.AQUA)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
public Updater(UpdaterConfig updaterConfig) {
|
|
||||||
this.updaterConfig = updaterConfig;
|
|
||||||
this.metadataRetriever = new MetadataRetriever(plugin, updaterConfig.channel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init() {
|
|
||||||
try {
|
|
||||||
DebugLogger.info("probing for channels...", 1);
|
|
||||||
|
|
||||||
List<String> channels = metadataRetriever.getChannels().join();
|
|
||||||
if (!channels.contains(updaterConfig.channel)) {
|
|
||||||
throw new UserError( // TODO replace this with its own error / exception?
|
|
||||||
"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("Ignore as you're running a development snapshot.", 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
DebugLogger.info("updater will notify", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
DebugLogger.info("updater loaded", 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<File> downloadUpdate() {
|
|
||||||
if (latestMetadata == null)
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<VersionMetadata> getLatestVersion() {
|
|
||||||
if (System.currentTimeMillis() < checkCacheExpires)
|
|
||||||
return CompletableFuture.completedFuture(latestMetadata);
|
|
||||||
else
|
|
||||||
return checkForNewVersion().handle((result, ex) -> null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* this can throw completionexception
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private CompletableFuture<VersionMetadata> checkForNewVersion() {
|
|
||||||
CompletableFuture<VersionMetadata> latestMetadataFuture =
|
|
||||||
metadataRetriever.getLatestVersionMetadata();
|
|
||||||
|
|
||||||
CompletableFuture<VersionMetadata> 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();
|
|
||||||
if (!player.hasPermission("realweather.update.notify")) return;
|
|
||||||
|
|
||||||
if (updateMessage != null) { // TODO make changelog somewhere
|
|
||||||
player.spigot().sendMessage(updateMessage);
|
|
||||||
if (player.hasPermission("realweather.admin"))
|
|
||||||
player.spigot().sendMessage(updateActionMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
DebugLogger.info("Checking for update...", 2);
|
|
||||||
|
|
||||||
CompletableFuture<VersionMetadata> newVersionFuture = checkForNewVersion();
|
|
||||||
|
|
||||||
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(latestMetadata.getFormattedDate()).color(ChatColor.AQUA)
|
|
||||||
.append("\nCurrent: ").color(ChatColor.GRAY)
|
|
||||||
.append(currentMetadata.label).color(ChatColor.DARK_AQUA)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
DebugLogger.info(updateMessage.toPlainText(), 0);
|
|
||||||
DebugLogger.info(updateActionMessage.toPlainText(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
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<VersionMetadata> currentMetadataCached = null;
|
|
||||||
|
|
||||||
public MetadataRetriever(Plugin plugin, String channel) {
|
|
||||||
this.version = plugin.getDescription().getVersion();
|
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getFileUrl(String version, String file) {
|
|
||||||
return "https://git.724.rocks/Minecon724/realweather-metadata/raw/branch/master/data/%s/%s/%s"
|
|
||||||
.formatted(channel, version, file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the completablefuture can throw a completionexception with {@link eu.m724.realweather.updater.metadata.MetadataServerException}
|
|
||||||
*/
|
|
||||||
private CompletableFuture<VersionMetadata> getMetadataOf(String version) {
|
|
||||||
String url = getFileUrl(version, "meta-v1.json");
|
|
||||||
|
|
||||||
HttpRequest request = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(url))
|
|
||||||
.header("User-Agent", "rwu/1") // real weather updater v1
|
|
||||||
.build();
|
|
||||||
|
|
||||||
CompletableFuture<HttpResponse<String>> responseFuture =
|
|
||||||
HttpClient.newBuilder()
|
|
||||||
.followRedirects(Redirect.NORMAL)
|
|
||||||
.proxy(ProxySelector.getDefault()).build().
|
|
||||||
sendAsync(request, BodyHandlers.ofString());
|
|
||||||
|
|
||||||
CompletableFuture<VersionMetadata> 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<List<String>> 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<HttpResponse<String>> responseFuture =
|
|
||||||
HttpClient.newBuilder()
|
|
||||||
.followRedirects(Redirect.NORMAL)
|
|
||||||
.proxy(ProxySelector.getDefault()).build().
|
|
||||||
sendAsync(request, BodyHandlers.ofString());
|
|
||||||
|
|
||||||
CompletableFuture<List<String>> 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<VersionMetadata> getLatestVersionMetadata() {
|
|
||||||
return getVersionMetadata("latest");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the completablefuture can throw a completionexception with {@link MetadataServerException}
|
|
||||||
*/
|
|
||||||
public CompletableFuture<VersionMetadata> getCurrentVersionMetadata() {
|
|
||||||
if (currentMetadataCached == null)
|
|
||||||
currentMetadataCached = getVersionMetadata(version);
|
|
||||||
return currentMetadataCached; // TODO reconsider this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the completablefuture can throw a completionexception with {@link MetadataServerException}
|
|
||||||
*/
|
|
||||||
public CompletableFuture<VersionMetadata> getVersionMetadata(String version) {
|
|
||||||
return getMetadataOf(version); // TODO remove this and rename that function?
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package eu.m724.realweather.updater.metadata;
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class VersionMetadata {
|
|
||||||
/**
|
|
||||||
* metadata file version
|
|
||||||
*/
|
|
||||||
public final int spec = 1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* version id. increments with each version
|
|
||||||
*/
|
|
||||||
public int id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* release time of a version
|
|
||||||
*/
|
|
||||||
public long timestamp;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* label aka version string
|
|
||||||
* example: 1.0.0
|
|
||||||
*/
|
|
||||||
public String label;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* url of the downloadable jar file
|
|
||||||
*/
|
|
||||||
public String fileUrl;
|
|
||||||
|
|
||||||
public String sha256;
|
|
||||||
|
|
||||||
public String getFormattedDate() {
|
|
||||||
return new SimpleDateFormat("dd.MM").format(new Date(timestamp));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue