Updated Version Checker

This commit is contained in:
jameslfc19 2020-07-29 01:40:57 +01:00
parent 4e7e17e864
commit f7be7366b9
4 changed files with 327 additions and 107 deletions

View File

@ -23,7 +23,7 @@ import com.jamesdpeters.minecraft.chests.serialize.LocationInfo;
import com.jamesdpeters.minecraft.chests.serialize.MaterialSerializer;
import com.jamesdpeters.minecraft.chests.serialize.RecipeSerializable;
import com.jamesdpeters.minecraft.chests.serialize.SpigotConfig;
import com.jamesdpeters.minecraft.chests.versionchecker.UpdateCheck;
import com.jamesdpeters.minecraft.chests.versionchecker.UpdateChecker;
import fr.minuskube.inv.InventoryManager;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
@ -109,44 +109,36 @@ public class ChestsPlusPlus extends JavaPlugin {
if(isBeta) getLogger().warning("You are currently running a Beta build - update checker disabled! Build: "+BuildConstants.VERSION);
if(Settings.isUpdateCheckEnabled() && !isDev && !isBeta) {
String SPIGOT_URL = "https://www.spigotmc.org/resources/chests-chest-linking-hopper-filtering-remote-chests-menus.71355/";
String BUKKIT_URL = "https://dev.bukkit.org/projects/chests-plus-plus/files";
UpdateCheck updateChecker = UpdateCheck
.of(this)
.resourceId(71355)
.currentVersion(getDescription().getVersion())
.handleResponse((versionResponse, version) -> {
switch (versionResponse) {
case FOUND_NEW:
getLogger().warning("New version of the plugin has been found: " + version);
getLogger().warning("Download at: "+SPIGOT_URL);
Bukkit.broadcastMessage(ChatColor.RED + "[Chests++] New version of the plugin was found: " + version);
Bukkit.broadcastMessage(ChatColor.RED + "[Chests++] Download at: " +ChatColor.WHITE+ BUKKIT_URL);
break;
case LATEST:
if(!boot) getLogger().info("Plugin is up to date! Thank you for supporting Chests++!");
break;
case UNAVAILABLE:
Bukkit.broadcastMessage("Unable to perform an update check.");
}
boot = true;
});
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, updateChecker::check,0,Settings.getUpdateCheckerPeriodTicks());
UpdateChecker.init(this, 71355, UpdateChecker.VERSION_SCHEME_DECIMAL);
Bukkit.getScheduler().runTaskTimerAsynchronously(this, () -> {
UpdateChecker.get().requestUpdateCheck().whenCompleteAsync((updateResult, throwable) -> {
switch (updateResult.getReason()) {
case NEW_UPDATE:
Bukkit.broadcastMessage(ChatColor.RED + "[Chests++] New version of the plugin was found: " + updateResult.getNewestVersion());
Bukkit.broadcastMessage(ChatColor.RED + "[Chests++] Download at: " + ChatColor.WHITE + BUKKIT_URL);
break;
case UP_TO_DATE:
if (!boot) getLogger().info("Plugin is up to date! Thank you for supporting Chests++!");
break;
}
boot = true;
});
}, 0, Settings.getUpdateCheckerPeriodTicks());
}
getLogger().info("Chests++ enabled!");
//Load storages after load.
Bukkit.getScheduler().scheduleSyncDelayedTask(this, () ->{
Crafting.load();
new Config();
getLogger().info("Chests++ Successfully Loaded Config and Recipes");
//Register event listeners
getServer().getPluginManager().registerEvents(new StorageListener(),this);
getServer().getPluginManager().registerEvents(new InventoryListener(),this);
getServer().getPluginManager().registerEvents(new HopperListener(),this);
getServer().getPluginManager().registerEvents(new WorldListener(),this);
getLogger().info("Chests++ Successfully Loaded Config and Recipes");
getLogger().info("Chests++ enabled!");
},1);
}

View File

@ -1,74 +0,0 @@
package com.jamesdpeters.minecraft.chests.versionchecker;
import com.google.common.base.Preconditions;
import com.google.common.io.Resources;
import com.google.common.net.HttpHeaders;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.function.BiConsumer;
import javax.net.ssl.HttpsURLConnection;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class UpdateCheck {
private static final String SPIGOT_URL = "https://api.spigotmc.org/legacy/update.php?resource=%d";
private final JavaPlugin javaPlugin;
private String currentVersion;
private int resourceId = -1;
private BiConsumer<VersionResponse, String> versionResponse;
private UpdateCheck(JavaPlugin javaPlugin) {
this.javaPlugin = Objects.requireNonNull(javaPlugin, "javaPlugin");
this.currentVersion = javaPlugin.getDescription().getVersion();
}
public static UpdateCheck of(JavaPlugin javaPlugin) {
return new UpdateCheck(javaPlugin);
}
public UpdateCheck currentVersion(String currentVersion) {
this.currentVersion = currentVersion;
return this;
}
public UpdateCheck resourceId(int resourceId) {
this.resourceId = resourceId;
return this;
}
public UpdateCheck handleResponse(BiConsumer<VersionResponse, String> versionResponse) {
this.versionResponse = versionResponse;
return this;
}
public void check() {
Objects.requireNonNull(this.javaPlugin, "javaPlugin");
Objects.requireNonNull(this.currentVersion, "currentVersion");
Preconditions.checkState(this.resourceId != -1, "resource id not set");
Objects.requireNonNull(this.versionResponse, "versionResponse");
Bukkit.getScheduler().runTaskAsynchronously(this.javaPlugin, () -> {
try {
HttpURLConnection httpURLConnection = (HttpsURLConnection) new URL(String.format(SPIGOT_URL, this.resourceId)).openConnection();
httpURLConnection.setRequestMethod("GET");
httpURLConnection.setRequestProperty(HttpHeaders.USER_AGENT, "Mozilla/5.0");
String fetchedVersion = Resources.toString(httpURLConnection.getURL(), Charset.defaultCharset());
boolean latestVersion = fetchedVersion.equalsIgnoreCase(this.currentVersion);
Bukkit.getScheduler().runTask(this.javaPlugin, () -> this.versionResponse.accept(latestVersion ? VersionResponse.LATEST : VersionResponse.FOUND_NEW, latestVersion ? this.currentVersion : fetchedVersion));
} catch (IOException exception) {
exception.printStackTrace();
Bukkit.getScheduler().runTask(this.javaPlugin, () -> this.versionResponse.accept(VersionResponse.UNAVAILABLE, null));
}
});
}
}

View File

@ -0,0 +1,309 @@
package com.jamesdpeters.minecraft.chests.versionchecker;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.CompletableFuture;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.google.common.base.Preconditions;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonSyntaxException;
import org.apache.commons.lang.math.NumberUtils;
import org.bukkit.plugin.java.JavaPlugin;
/**
* A utility class to assist in checking for updates for plugins uploaded to
* <a href="https://spigotmc.org/resources/">SpigotMC</a>. Before any members of this
* class are accessed, {@link #init(JavaPlugin, int)} must be invoked by the plugin,
* preferrably in its {@link JavaPlugin#onEnable()} method, though that is not a
* requirement.
* <p>
* This class performs asynchronous queries to <a href="https://spiget.org">SpiGet</a>,
* an REST server which is updated periodically. If the results of {@link #requestUpdateCheck()}
* are inconsistent with what is published on SpigotMC, it may be due to SpiGet's cache.
* Results will be updated in due time.
*
* @author Parker Hawke - 2008Choco
*/
public final class UpdateChecker {
public static final VersionScheme VERSION_SCHEME_DECIMAL = (first, second) -> {
String[] firstSplit = splitVersionInfo(first), secondSplit = splitVersionInfo(second);
if (firstSplit == null || secondSplit == null) return null;
for (int i = 0; i < Math.min(firstSplit.length, secondSplit.length); i++) {
int currentValue = NumberUtils.toInt(firstSplit[i]), newestValue = NumberUtils.toInt(secondSplit[i]);
if (newestValue > currentValue) {
return second;
} else if (newestValue < currentValue) {
return first;
}
}
return (secondSplit.length > firstSplit.length) ? second : first;
};
private static final String USER_AGENT = "CHOCO-update-checker";
private static final String UPDATE_URL = "https://api.spigotmc.org/simple/0.1/index.php?action=getResource&id=%d";
private static final Pattern DECIMAL_SCHEME_PATTERN = Pattern.compile("\\d+(?:\\.\\d+)*");
private static UpdateChecker instance;
private UpdateResult lastResult = null;
private final JavaPlugin plugin;
private final int pluginID;
private final VersionScheme versionScheme;
private UpdateChecker(JavaPlugin plugin, int pluginID, VersionScheme versionScheme) {
this.plugin = plugin;
this.pluginID = pluginID;
this.versionScheme = versionScheme;
}
/**
* Request an update check to SpiGet. This request is asynchronous and may not complete
* immediately as an HTTP GET request is published to the SpiGet API.
*
* @return a future update result
*/
public CompletableFuture<UpdateResult> requestUpdateCheck() {
return CompletableFuture.supplyAsync(() -> {
int responseCode = -1;
try {
URL url = new URL(String.format(UPDATE_URL, pluginID));
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("User-Agent", USER_AGENT);
InputStreamReader reader = new InputStreamReader(connection.getInputStream());
responseCode = connection.getResponseCode();
JsonElement element = new JsonParser().parse(reader);
reader.close();
JsonObject versionObject = element.getAsJsonObject();
String current = plugin.getDescription().getVersion(), newest = versionObject.get("current_version").getAsString();
String latest = versionScheme.compareVersions(current, newest);
if (latest == null) {
return new UpdateResult(UpdateReason.UNSUPPORTED_VERSION_SCHEME);
} else if (latest.equals(current)) {
return new UpdateResult(current.equals(newest) ? UpdateReason.UP_TO_DATE : UpdateReason.UNRELEASED_VERSION);
} else if (latest.equals(newest)) {
return new UpdateResult(UpdateReason.NEW_UPDATE, latest);
}
} catch (IOException e) {
return new UpdateResult(UpdateReason.COULD_NOT_CONNECT);
} catch (JsonSyntaxException e) {
return new UpdateResult(UpdateReason.INVALID_JSON);
}
return new UpdateResult(responseCode == 401 ? UpdateReason.UNAUTHORIZED_QUERY : UpdateReason.UNKNOWN_ERROR);
});
}
/**
* Get the last update result that was queried by {@link #requestUpdateCheck()}. If no update
* check was performed since this class' initialization, this method will return null.
*
* @return the last update check result. null if none.
*/
public UpdateResult getLastResult() {
return lastResult;
}
private static String[] splitVersionInfo(String version) {
Matcher matcher = DECIMAL_SCHEME_PATTERN.matcher(version);
if (!matcher.find()) return null;
return matcher.group().split("\\.");
}
/**
* Initialize this update checker with the specified values and return its instance. If an instance
* of UpdateChecker has already been initialized, this method will act similarly to {@link #get()}
* (which is recommended after initialization).
*
* @param plugin the plugin for which to check updates. Cannot be null
* @param pluginID the ID of the plugin as identified in the SpigotMC resource link. For example,
* "https://www.spigotmc.org/resources/veinminer.<b>12038</b>/" would expect "12038" as a value. The
* value must be greater than 0
* @param versionScheme a custom version scheme parser. Cannot be null
*
* @return the UpdateChecker instance
*/
public static UpdateChecker init(JavaPlugin plugin, int pluginID, VersionScheme versionScheme) {
Preconditions.checkArgument(plugin != null, "Plugin cannot be null");
Preconditions.checkArgument(pluginID > 0, "Plugin ID must be greater than 0");
Preconditions.checkArgument(versionScheme != null, "null version schemes are unsupported");
return (instance == null) ? instance = new UpdateChecker(plugin, pluginID, versionScheme) : instance;
}
/**
* Initialize this update checker with the specified values and return its instance. If an instance
* of UpdateChecker has already been initialized, this method will act similarly to {@link #get()}
* (which is recommended after initialization).
*
* @param plugin the plugin for which to check updates. Cannot be null
* @param pluginID the ID of the plugin as identified in the SpigotMC resource link. For example,
* "https://www.spigotmc.org/resources/veinminer.<b>12038</b>/" would expect "12038" as a value. The
* value must be greater than 0
*
* @return the UpdateChecker instance
*/
public static UpdateChecker init(JavaPlugin plugin, int pluginID) {
return init(plugin, pluginID, VERSION_SCHEME_DECIMAL);
}
/**
* Get the initialized instance of UpdateChecker. If {@link #init(JavaPlugin, int)} has not yet been
* invoked, this method will throw an exception.
*
* @return the UpdateChecker instance
*/
public static UpdateChecker get() {
Preconditions.checkState(instance != null, "Instance has not yet been initialized. Be sure #init() has been invoked");
return instance;
}
/**
* Check whether the UpdateChecker has been initialized or not (if {@link #init(JavaPlugin, int)}
* has been invoked) and {@link #get()} is safe to use.
*
* @return true if initialized, false otherwise
*/
public static boolean isInitialized() {
return instance != null;
}
/**
* A functional interface to compare two version Strings with similar version schemes.
*/
@FunctionalInterface
public static interface VersionScheme {
/**
* Compare two versions and return the higher of the two. If null is returned, it is assumed
* that at least one of the two versions are unsupported by this version scheme parser.
*
* @param first the first version to check
* @param second the second version to check
*
* @return the greater of the two versions. null if unsupported version schemes
*/
public String compareVersions(String first, String second);
}
/**
* A constant reason for the result of {@link UpdateResult}.
*/
public static enum UpdateReason {
/**
* A new update is available for download on SpigotMC.
*/
NEW_UPDATE, // The only reason that requires an update
/**
* A successful connection to the SpiGet API could not be established.
*/
COULD_NOT_CONNECT,
/**
* The JSON retrieved from SpiGet was invalid or malformed.
*/
INVALID_JSON,
/**
* A 401 error was returned by the SpiGet API.
*/
UNAUTHORIZED_QUERY,
/**
* The version of the plugin installed on the server is greater than the one uploaded
* to SpigotMC's resources section.
*/
UNRELEASED_VERSION,
/**
* An unknown error occurred.
*/
UNKNOWN_ERROR,
/**
* The plugin uses an unsupported version scheme, therefore a proper comparison between
* versions could not be made.
*/
UNSUPPORTED_VERSION_SCHEME,
/**
* The plugin is up to date with the version released on SpigotMC's resources section.
*/
UP_TO_DATE;
}
/**
* Represents a result for an update query performed by {@link UpdateChecker#requestUpdateCheck()}.
*/
public final class UpdateResult {
private final UpdateReason reason;
private final String newestVersion;
{ // An actual use for initializer blocks. This is madness!
UpdateChecker.this.lastResult = this;
}
private UpdateResult(UpdateReason reason, String newestVersion) {
this.reason = reason;
this.newestVersion = newestVersion;
}
private UpdateResult(UpdateReason reason) {
Preconditions.checkArgument(reason != UpdateReason.NEW_UPDATE, "Reasons that require updates must also provide the latest version String");
this.reason = reason;
this.newestVersion = plugin.getDescription().getVersion();
}
/**
* Get the constant reason of this result.
*
* @return the reason
*/
public UpdateReason getReason() {
return reason;
}
/**
* Check whether or not this result requires the user to update.
*
* @return true if requires update, false otherwise
*/
public boolean requiresUpdate() {
return reason == UpdateReason.NEW_UPDATE;
}
/**
* Get the latest version of the plugin. This may be the currently installed version, it
* may not be. This depends entirely on the result of the update.
*
* @return the newest version of the plugin
*/
public String getNewestVersion() {
return newestVersion;
}
}
}

View File

@ -1,7 +0,0 @@
package com.jamesdpeters.minecraft.chests.versionchecker;
public enum VersionResponse {
LATEST,
FOUND_NEW,
UNAVAILABLE
}