mirror of
https://github.com/TotalFreedomMC/TF-EssentialsX.git
synced 2025-01-03 22:08:28 +00:00
Add update checker (#3855)
Co-authored-by: MD <1917406+mdcfe@users.noreply.github.com> Adds an update checker to Essentials that will check for the latest version on startup, on player join (permission is `essentials.updatecheck`), or manually with `/ess version`. On startup, the update checker will fetch build information from a resource generated at compile time and determine if the build is a dev or stable build. If it's a stable build, the update checker will only check for a new release; while a dev build will check for any new commits. There are 6 different types of messages the update checker will return; * Identical: The current build is the latest stable release or latest dev build. This message is only shown in the `/ess version` command. * Behind: If the current build is stable, it's an entire stable build behind, otherwise it's one or more dev builds behind. * Diverged: The current build was made from a branch other than `2.x` and is also one or more dev builds behind the latest commit on `2.x`. * Diverged Latest: The current build was made from a branch other than `2.x` but is based on the latest commit from `2.x`. * Unknown: The current build either has invalid build information or was customly built. This message is show everywhere but on player join. * Error: There was an error while fetching the latest version information. Update checks can be disabled using the `update-check` option in `config.yml`.
This commit is contained in:
parent
b43790e9d2
commit
10fa3b5a31
12 changed files with 342 additions and 3 deletions
|
@ -36,6 +36,7 @@ import com.earth2me.essentials.signs.SignPlayerListener;
|
|||
import com.earth2me.essentials.textreader.IText;
|
||||
import com.earth2me.essentials.textreader.KeywordReplacer;
|
||||
import com.earth2me.essentials.textreader.SimpleTextInput;
|
||||
import com.earth2me.essentials.updatecheck.UpdateChecker;
|
||||
import com.earth2me.essentials.utils.VersionUtil;
|
||||
import io.papermc.lib.PaperLib;
|
||||
import net.ess3.api.Economy;
|
||||
|
@ -147,6 +148,7 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
|
|||
private transient MaterialTagProvider materialTagProvider;
|
||||
private transient Kits kits;
|
||||
private transient RandomTeleport randomTeleport;
|
||||
private transient UpdateChecker updateChecker;
|
||||
|
||||
static {
|
||||
// TODO: improve legacy code
|
||||
|
@ -390,6 +392,16 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
|
|||
|
||||
metrics = new MetricsWrapper(this, 858, true);
|
||||
|
||||
updateChecker = new UpdateChecker(this);
|
||||
runTaskAsynchronously(() -> {
|
||||
LOGGER.log(Level.INFO, tl("versionFetching"));
|
||||
for (String str : updateChecker.getVersionMessages(false, true)) {
|
||||
LOGGER.log(Level.WARNING, str);
|
||||
}
|
||||
});
|
||||
|
||||
execTimer.mark("Init(External)");
|
||||
|
||||
final String timeroutput = execTimer.end();
|
||||
if (getSettings().isDebug()) {
|
||||
LOGGER.log(Level.INFO, "Essentials load {0}", timeroutput);
|
||||
|
@ -776,6 +788,11 @@ public class Essentials extends JavaPlugin implements net.ess3.api.IEssentials {
|
|||
return randomTeleport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UpdateChecker getUpdateChecker() {
|
||||
return updateChecker;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public User getUser(final Object base) {
|
||||
|
|
|
@ -422,6 +422,14 @@ public class EssentialsPlayerListener implements Listener {
|
|||
final TextPager pager = new TextPager(output, true);
|
||||
pager.showPage("1", null, "motd", user.getSource());
|
||||
}
|
||||
|
||||
if (user.isAuthorized("essentials.updatecheck")) {
|
||||
ess.runTaskAsynchronously(() -> {
|
||||
for (String str : ess.getUpdateChecker().getVersionMessages(false, false)) {
|
||||
user.sendMessage(str);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import com.earth2me.essentials.api.IItemDb;
|
|||
import com.earth2me.essentials.api.IJails;
|
||||
import com.earth2me.essentials.api.IWarps;
|
||||
import com.earth2me.essentials.perm.PermissionsHandler;
|
||||
import com.earth2me.essentials.updatecheck.UpdateChecker;
|
||||
import net.ess3.provider.MaterialTagProvider;
|
||||
import net.ess3.provider.ContainerProvider;
|
||||
import net.ess3.provider.FormattedCommandAliasProvider;
|
||||
|
@ -73,6 +74,8 @@ public interface IEssentials extends Plugin {
|
|||
|
||||
RandomTeleport getRandomTeleport();
|
||||
|
||||
UpdateChecker getUpdateChecker();
|
||||
|
||||
BukkitTask runTaskAsynchronously(Runnable run);
|
||||
|
||||
BukkitTask runTaskLaterAsynchronously(Runnable run, long delay);
|
||||
|
|
|
@ -391,6 +391,8 @@ public interface ISettings extends IConf {
|
|||
|
||||
boolean isRespawnAtBed();
|
||||
|
||||
boolean isUpdateCheckEnabled();
|
||||
|
||||
enum KeepInvPolicy {
|
||||
KEEP,
|
||||
DELETE,
|
||||
|
|
|
@ -1767,4 +1767,9 @@ public class Settings implements net.ess3.api.ISettings {
|
|||
public boolean isRespawnAtBed() {
|
||||
return config.getBoolean("respawn-at-home-bed", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUpdateCheckEnabled() {
|
||||
return config.getBoolean("update-check", true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -388,10 +388,16 @@ public class Commandessentials extends EssentialsCommand {
|
|||
sender.sendMessage(tl("serverUnsupportedLimitedApi"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (VersionUtil.getSupportStatusClass() != null) {
|
||||
sender.sendMessage(tl("serverUnsupportedClass", VersionUtil.getSupportStatusClass()));
|
||||
}
|
||||
|
||||
sender.sendMessage(tl("versionFetching"));
|
||||
ess.runTaskAsynchronously(() -> {
|
||||
for (String str : ess.getUpdateChecker().getVersionMessages(true, true)) {
|
||||
sender.sendMessage(str);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -38,6 +38,7 @@ public class MetricsWrapper {
|
|||
checkForcedMetrics();
|
||||
addPermsChart();
|
||||
addEconomyChart();
|
||||
addReleaseBranchChart();
|
||||
|
||||
// bStats' backend currently doesn't support multi-line charts or advanced bar charts
|
||||
// These are included for when bStats is ready to accept this data
|
||||
|
@ -87,6 +88,10 @@ public class MetricsWrapper {
|
|||
}));
|
||||
}
|
||||
|
||||
private void addReleaseBranchChart() {
|
||||
metrics.addCustomChart(new Metrics.SimplePie("releaseBranch", ess.getUpdateChecker()::getVersionBranch));
|
||||
}
|
||||
|
||||
private void addCommandsChart() {
|
||||
for (final String command : plugin.getDescription().getCommands().keySet()) {
|
||||
markCommand(command, false);
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
package com.earth2me.essentials.updatecheck;
|
||||
|
||||
import com.earth2me.essentials.Essentials;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.earth2me.essentials.I18n.tl;
|
||||
|
||||
public final class UpdateChecker {
|
||||
private static final String REPO = "EssentialsX/Essentials";
|
||||
private static final String BRANCH = "2.x";
|
||||
|
||||
private final Essentials ess;
|
||||
private final String versionIdentifier;
|
||||
private final String versionBranch;
|
||||
private final boolean devBuild;
|
||||
|
||||
private long lastFetchTime = 0;
|
||||
private CompletableFuture<RemoteVersion> pendingDevFuture;
|
||||
private CompletableFuture<RemoteVersion> pendingReleaseFuture;
|
||||
private String latestRelease = null;
|
||||
private RemoteVersion cachedDev = null;
|
||||
private RemoteVersion cachedRelease = null;
|
||||
|
||||
public UpdateChecker(Essentials ess) {
|
||||
String identifier = "INVALID";
|
||||
String branch = "INVALID";
|
||||
boolean dev = false;
|
||||
|
||||
final InputStream inputStream = UpdateChecker.class.getClassLoader().getResourceAsStream("release");
|
||||
if (inputStream != null) {
|
||||
final List<String> versionStr = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines().collect(Collectors.toList());
|
||||
if (versionStr.size() == 2) {
|
||||
if (versionStr.get(0).matches("\\d+\\.\\d+\\.\\d+-dev\\+\\d\\d-[0-9a-f]{7,40}")) {
|
||||
identifier = versionStr.get(0).split("-")[2];
|
||||
dev = true;
|
||||
} else {
|
||||
identifier = versionStr.get(0);
|
||||
}
|
||||
branch = versionStr.get(1);
|
||||
}
|
||||
}
|
||||
|
||||
this.ess = ess;
|
||||
this.versionIdentifier = identifier;
|
||||
this.versionBranch = branch;
|
||||
this.devBuild = dev;
|
||||
}
|
||||
|
||||
public boolean isDevBuild() {
|
||||
return devBuild;
|
||||
}
|
||||
|
||||
public CompletableFuture<RemoteVersion> fetchLatestDev() {
|
||||
if (cachedDev == null || ((System.currentTimeMillis() - lastFetchTime) > 1800000L)) {
|
||||
if (pendingDevFuture != null) {
|
||||
return pendingDevFuture;
|
||||
}
|
||||
pendingDevFuture = new CompletableFuture<>();
|
||||
new Thread(() -> {
|
||||
pendingDevFuture.complete(cachedDev = fetchDistance(BRANCH, getVersionIdentifier()));
|
||||
pendingDevFuture = null;
|
||||
lastFetchTime = System.currentTimeMillis();
|
||||
}).start();
|
||||
return pendingDevFuture;
|
||||
}
|
||||
return CompletableFuture.completedFuture(cachedDev);
|
||||
}
|
||||
|
||||
public CompletableFuture<RemoteVersion> fetchLatestRelease() {
|
||||
if (cachedRelease == null || ((System.currentTimeMillis() - lastFetchTime) > 1800000L)) {
|
||||
if (pendingReleaseFuture != null) {
|
||||
return pendingReleaseFuture;
|
||||
}
|
||||
pendingReleaseFuture = new CompletableFuture<>();
|
||||
new Thread(() -> {
|
||||
catchBlock:
|
||||
try {
|
||||
final HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + REPO + "/releases/latest").openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
// Locally built?
|
||||
pendingReleaseFuture.complete(cachedRelease = new RemoteVersion(BranchStatus.UNKNOWN));
|
||||
break catchBlock;
|
||||
}
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) {
|
||||
// Github is down
|
||||
pendingReleaseFuture.complete(new RemoteVersion(BranchStatus.ERROR));
|
||||
break catchBlock;
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
|
||||
latestRelease = new Gson().fromJson(reader, JsonObject.class).get("tag_name").getAsString();
|
||||
pendingReleaseFuture.complete(cachedRelease = fetchDistance(latestRelease, getVersionIdentifier()));
|
||||
} catch (JsonSyntaxException | NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
pendingReleaseFuture.complete(new RemoteVersion(BranchStatus.ERROR));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
pendingReleaseFuture.complete(new RemoteVersion(BranchStatus.ERROR));
|
||||
}
|
||||
pendingReleaseFuture = null;
|
||||
lastFetchTime = System.currentTimeMillis();
|
||||
}).start();
|
||||
return pendingReleaseFuture;
|
||||
}
|
||||
return CompletableFuture.completedFuture(cachedRelease);
|
||||
}
|
||||
|
||||
public String getVersionIdentifier() {
|
||||
return versionIdentifier;
|
||||
}
|
||||
|
||||
public String getVersionBranch() {
|
||||
return versionBranch;
|
||||
}
|
||||
|
||||
public String getBuildInfo() {
|
||||
return "id:'" + getVersionIdentifier() + "' branch:'" + getVersionBranch() + "' isDev:" + isDevBuild();
|
||||
}
|
||||
|
||||
public String getLatestRelease() {
|
||||
return latestRelease;
|
||||
}
|
||||
|
||||
private RemoteVersion fetchDistance(final String head, final String hash) {
|
||||
try {
|
||||
final HttpURLConnection connection = (HttpURLConnection) new URL("https://api.github.com/repos/" + REPO + "/compare/" + head + "..." + hash).openConnection();
|
||||
connection.connect();
|
||||
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
// Locally built?
|
||||
return new RemoteVersion(BranchStatus.UNKNOWN);
|
||||
}
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_INTERNAL_ERROR) {
|
||||
// Github is down
|
||||
return new RemoteVersion(BranchStatus.ERROR);
|
||||
}
|
||||
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
|
||||
final JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
|
||||
switch (obj.get("status").getAsString()) {
|
||||
case "identical": {
|
||||
return new RemoteVersion(BranchStatus.IDENTICAL, 0);
|
||||
}
|
||||
case "ahead": {
|
||||
return new RemoteVersion(BranchStatus.AHEAD, 0);
|
||||
}
|
||||
case "behind": {
|
||||
return new RemoteVersion(BranchStatus.BEHIND, obj.get("behind_by").getAsInt());
|
||||
}
|
||||
case "diverged": {
|
||||
return new RemoteVersion(BranchStatus.DIVERGED, obj.get("behind_by").getAsInt());
|
||||
}
|
||||
default: {
|
||||
return new RemoteVersion(BranchStatus.UNKNOWN);
|
||||
}
|
||||
}
|
||||
} catch (JsonSyntaxException | NumberFormatException e) {
|
||||
e.printStackTrace();
|
||||
return new RemoteVersion(BranchStatus.ERROR);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return new RemoteVersion(BranchStatus.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
public String[] getVersionMessages(final boolean sendLatestMessage, final boolean verboseErrors) {
|
||||
if (!ess.getSettings().isUpdateCheckEnabled()) {
|
||||
return new String[] {tl("versionCheckDisabled")};
|
||||
}
|
||||
|
||||
if (this.isDevBuild()) {
|
||||
final RemoteVersion latestDev = this.fetchLatestDev().join();
|
||||
switch (latestDev.getBranchStatus()) {
|
||||
case IDENTICAL: {
|
||||
return sendLatestMessage ? new String[] {tl("versionDevLatest")} : new String[] {};
|
||||
}
|
||||
case BEHIND: {
|
||||
return new String[] {tl("versionDevBehind", latestDev.getDistance()),
|
||||
tl("versionReleaseNewLink", "https://essentialsx.net/downloads.html")};
|
||||
}
|
||||
case AHEAD:
|
||||
case DIVERGED: {
|
||||
return new String[] {tl(latestDev.getDistance() == 0 ? "versionDevDivergedLatest" : "versionDevDiverged", latestDev.getDistance()),
|
||||
tl("versionDevDivergedBranch", this.getVersionBranch()) };
|
||||
}
|
||||
case UNKNOWN: {
|
||||
return verboseErrors ? new String[] {tl("versionCustom", this.getBuildInfo())} : new String[] {};
|
||||
}
|
||||
case ERROR: {
|
||||
return new String[] {tl(verboseErrors ? "versionError" : "versionErrorPlayer", this.getBuildInfo())};
|
||||
}
|
||||
default: {
|
||||
return new String[] {};
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final RemoteVersion latestRelease = this.fetchLatestRelease().join();
|
||||
switch (latestRelease.getBranchStatus()) {
|
||||
case IDENTICAL: {
|
||||
return sendLatestMessage ? new String[] {tl("versionReleaseLatest")} : new String[] {};
|
||||
}
|
||||
case BEHIND: {
|
||||
return new String[] {tl("versionReleaseNew", this.getLatestRelease()),
|
||||
tl("versionReleaseNewLink", "https://essentialsx.net/downloads.html?branch=stable")};
|
||||
}
|
||||
case DIVERGED: //WhatChamp
|
||||
case AHEAD: //monkaW?
|
||||
case UNKNOWN: {
|
||||
return verboseErrors ? new String[] {tl("versionCustom", this.getBuildInfo())} : new String[] {};
|
||||
}
|
||||
case ERROR: {
|
||||
return new String[] {tl(verboseErrors ? "versionError" : "versionErrorPlayer", this.getBuildInfo())};
|
||||
}
|
||||
default: {
|
||||
return new String[] {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class RemoteVersion {
|
||||
private final BranchStatus branchStatus;
|
||||
private final int distance;
|
||||
|
||||
RemoteVersion(BranchStatus branchStatus) {
|
||||
this(branchStatus, 0);
|
||||
}
|
||||
|
||||
RemoteVersion(BranchStatus branchStatus, int distance) {
|
||||
this.branchStatus = branchStatus;
|
||||
this.distance = distance;
|
||||
}
|
||||
|
||||
public BranchStatus getBranchStatus() {
|
||||
return branchStatus;
|
||||
}
|
||||
|
||||
public int getDistance() {
|
||||
return distance;
|
||||
}
|
||||
}
|
||||
|
||||
private enum BranchStatus {
|
||||
IDENTICAL,
|
||||
AHEAD,
|
||||
BEHIND,
|
||||
DIVERGED,
|
||||
ERROR,
|
||||
UNKNOWN
|
||||
}
|
||||
}
|
|
@ -674,6 +674,11 @@ log-command-block-commands: true
|
|||
# Set the maximum speed for projectiles spawned with /fireball.
|
||||
max-projectile-speed: 8
|
||||
|
||||
# Should EssentialsX check for updates?
|
||||
# If set to true, EssentialsX will show notifications when a new version is available.
|
||||
# This uses the public GitHub API and no identifying information is sent or stored.
|
||||
update-check: true
|
||||
|
||||
############################################################
|
||||
# +------------------------------------------------------+ #
|
||||
# | Homes | #
|
||||
|
|
|
@ -936,6 +936,16 @@ vanish=\u00a76Vanish for {0}\u00a76\: {1}
|
|||
vanishCommandDescription=Hide yourself from other players.
|
||||
vanishCommandUsage=/<command> [player] [on|off]
|
||||
vanished=\u00a76You are now completely invisible to normal users, and hidden from in-game commands.
|
||||
versionCheckDisabled=\u00a76Update checking disabled in config.
|
||||
versionCustom=\u00a76Unable to check your version! Self-built? Build information: \u00a7c{0}\u00a76.
|
||||
versionDevBehind=\u00a74You''re \u00a7c{0} \u00a74EssentialsX dev build(s) out of date!
|
||||
versionDevDiverged=\u00a76You''re running an experimental build of EssentialsX that is \u00a7c{0} \u00a76builds behind the latest dev build!
|
||||
versionDevDivergedBranch=\u00a76Feature Branch: \u00a7c{0}\u00a76.
|
||||
versionDevDivergedLatest=\u00a76You''re running an up to date experimental EssentialsX build!
|
||||
versionDevLatest=\u00a76You''re running the latest EssentialsX dev build!
|
||||
versionError=\u00a74Error while fetching EssentialsX version information! Build information: \u00a7c{0}\u00a76.
|
||||
versionErrorPlayer=\u00a76Error while checking EssentialsX version information!
|
||||
versionFetching=\u00a76Fetching version information...
|
||||
versionOutputVaultMissing=\u00a74Vault is not installed. Chat and permissions may not work.
|
||||
versionOutputFine=\u00a76{0} version: \u00a7a{1}
|
||||
versionOutputWarn=\u00a76{0} version: \u00a7c{1}
|
||||
|
@ -943,6 +953,9 @@ versionOutputUnsupported=\u00a7d{0} \u00a76version: \u00a7d{1}
|
|||
versionOutputUnsupportedPlugins=\u00a76You are running \u00a7dunsupported plugins\u00a76!
|
||||
versionMismatch=\u00a74Version mismatch\! Please update {0} to the same version.
|
||||
versionMismatchAll=\u00a74Version mismatch\! Please update all Essentials jars to the same version.
|
||||
versionReleaseLatest=\u00a76You''re running the latest stable version of EssentialsX!
|
||||
versionReleaseNew=\u00a74There is a new EssentialsX version available for download: \u00a7c{0}\u00a74.
|
||||
versionReleaseNewLink=\u00a74Download it here:\u00a7c {0}
|
||||
voiceSilenced=\u00a76Your voice has been silenced\!
|
||||
voiceSilencedTime=\u00a76Your voice has been silenced for {0}\!
|
||||
voiceSilencedReason=\u00a76Your voice has been silenced\! Reason: \u00a7c{0}
|
||||
|
|
2
Essentials/src/main/resources/release
Normal file
2
Essentials/src/main/resources/release
Normal file
|
@ -0,0 +1,2 @@
|
|||
${full.version}
|
||||
${git.branch}
|
|
@ -36,9 +36,10 @@ def commitsSinceLastTag() {
|
|||
|
||||
ext {
|
||||
GIT_COMMIT = grgit == null ? "unknown" : grgit.head().abbreviatedId
|
||||
GIT_BRANCH = grgit == null ? "detached-head" : grgit.branch.current().name
|
||||
GIT_DEPTH = commitsSinceLastTag()
|
||||
|
||||
fullVersion = "${version}-${GIT_COMMIT}".replace("-SNAPSHOT", "-dev+${GIT_DEPTH}")
|
||||
fullVersion = "${version}".replace("-SNAPSHOT", "-dev+${GIT_DEPTH}-${GIT_COMMIT}")
|
||||
|
||||
checkstyleVersion = '8.36.2'
|
||||
spigotVersion = '1.16.5-R0.1-SNAPSHOT'
|
||||
|
@ -92,9 +93,12 @@ subprojects {
|
|||
|
||||
// Version Injection
|
||||
processResources {
|
||||
// Always process resources if version string or git branch changes
|
||||
inputs.property('fullVersion', fullVersion)
|
||||
inputs.property('gitBranch', GIT_BRANCH)
|
||||
filter(ReplaceTokens, beginToken: '${',
|
||||
endToken: '}', tokens: ["full.version": fullVersion])
|
||||
endToken: '}', tokens: ["full.version": fullVersion, "git.branch": GIT_BRANCH])
|
||||
|
||||
}
|
||||
|
||||
indra {
|
||||
|
|
Loading…
Reference in a new issue