9
0
mirror of https://github.com/LeavesMC/Leaves.git synced 2025-12-20 15:29:35 +00:00
Files
LeavesMC/patches/server/0107-Leaves-update-command.patch
2023-09-28 18:12:42 +08:00

294 lines
12 KiB
Diff

From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: violetc <58360096+s-yh-china@users.noreply.github.com>
Date: Mon, 31 Jul 2023 17:42:25 +0800
Subject: [PATCH] Leaves update command
diff --git a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java
index e89bf96486c87cdff6c1a425afd10d744376c77f..8027a27b253db02570c2f28ebb200001b76a1fc3 100644
--- a/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java
+++ b/src/main/java/top/leavesmc/leaves/command/LeavesCommand.java
@@ -12,6 +12,7 @@ import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.command.subcommands.UpdateCommand;
import java.util.ArrayList;
import java.util.Arrays;
@@ -31,6 +32,7 @@ public final class LeavesCommand extends Command {
// subcommand label -> subcommand
private static final Map<String, LeavesSubcommand> SUBCOMMANDS = Util.make(() -> {
final Map<Set<String>, LeavesSubcommand> commands = new HashMap<>();
+ commands.put(Set.of("update"), new UpdateCommand());
return commands.entrySet().stream()
.flatMap(entry -> entry.getKey().stream().map(s -> Map.entry(s, entry.getValue())))
diff --git a/src/main/java/top/leavesmc/leaves/command/subcommands/UpdateCommand.java b/src/main/java/top/leavesmc/leaves/command/subcommands/UpdateCommand.java
new file mode 100644
index 0000000000000000000000000000000000000000..293117a6c54fdce70d69b93fe80ce0216f9b3ea2
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/command/subcommands/UpdateCommand.java
@@ -0,0 +1,21 @@
+package top.leavesmc.leaves.command.subcommands;
+
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import top.leavesmc.leaves.command.LeavesSubcommand;
+import top.leavesmc.leaves.util.LeavesUpdateHelper;
+
+public class UpdateCommand implements LeavesSubcommand {
+
+ @Override
+ public boolean execute(CommandSender sender, String subCommand, String[] args) {
+ sender.sendMessage(ChatColor.GRAY + "Trying to update Leaves, see the console for more info.");
+ LeavesUpdateHelper.tryUpdateLeaves();
+ return true;
+ }
+
+ @Override
+ public boolean tabCompletes() {
+ return false;
+ }
+}
diff --git a/src/main/java/top/leavesmc/leaves/util/LeavesUpdateHelper.java b/src/main/java/top/leavesmc/leaves/util/LeavesUpdateHelper.java
new file mode 100644
index 0000000000000000000000000000000000000000..7476f6c5bf8c4572878159a74507193e3c4a6207
--- /dev/null
+++ b/src/main/java/top/leavesmc/leaves/util/LeavesUpdateHelper.java
@@ -0,0 +1,234 @@
+package top.leavesmc.leaves.util;
+
+import com.google.common.base.Charsets;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonSyntaxException;
+import net.minecraft.Util;
+import org.bukkit.Bukkit;
+import org.jetbrains.annotations.NotNull;
+import top.leavesmc.leaves.LeavesLogger;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.MessageDigest;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static java.nio.file.StandardOpenOption.CREATE;
+import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
+import static java.nio.file.StandardOpenOption.WRITE;
+
+public class LeavesUpdateHelper {
+
+ private final static String autoUpdateDir = "auto_update";
+ private final static String corePathFileName = autoUpdateDir + File.separator + "core.path";
+
+ private final static ReentrantLock updateLock = new ReentrantLock();
+ private static boolean updateTaskStarted = false;
+
+ public static void initAutoUpdate() {
+ File workingDirFile = new File(autoUpdateDir);
+ if (!workingDirFile.exists()) {
+ workingDirFile.mkdir();
+ }
+
+ File corePathFile = new File(corePathFileName);
+ if (!corePathFile.exists()) {
+ try {
+ corePathFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ File leavesUpdateDir = new File(autoUpdateDir + File.separator + "leaves");
+ if (!leavesUpdateDir.exists()) {
+ leavesUpdateDir.mkdir();
+ }
+ }
+
+ public static void tryUpdateLeaves() {
+ updateLock.lock();
+ try {
+ if (!updateTaskStarted) {
+ updateTaskStarted = true;
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ downloadLeaves();
+ }
+ }).start();
+ }
+ } finally {
+ updateLock.unlock();
+ }
+ }
+
+ private static void downloadLeaves() {
+ String minecraftVersion = Bukkit.getMinecraftVersion();
+ String version = Bukkit.getVersion();
+
+ if (version.startsWith("null")) {
+ LeavesLogger.LOGGER.info("IDE?");
+ updateTaskStarted = false;
+ return;
+ }
+
+ String gitHash = version.substring("git-Leaves-".length()).split("[-\\s]")[0].replaceAll("\"", "");
+
+ LeavesLogger.LOGGER.info("Trying to get latest build info.");
+ LeavesBuildInfo buildInfo = getLatestBuildInfo(minecraftVersion, gitHash);
+
+ if (buildInfo != LeavesBuildInfo.NULL) {
+ if (!buildInfo.needUpdate) {
+ LeavesLogger.LOGGER.warning("You are running the latest version, stopping update.");
+ updateTaskStarted = false;
+ return;
+ }
+
+ LeavesLogger.LOGGER.info("Got build info, trying to download " + buildInfo.fileName);
+ try {
+ Path outFile = Path.of(autoUpdateDir, "leaves", buildInfo.fileName + ".cache");
+ Files.deleteIfExists(outFile);
+
+ boolean downloadFlag = false;
+ URL cdnUrl = new URL(buildInfo.cdnUrl);
+ try (
+ final ReadableByteChannel source = Channels.newChannel(cdnUrl.openStream());
+ final FileChannel fileChannel = FileChannel.open(outFile, CREATE, WRITE, TRUNCATE_EXISTING)
+ ) {
+ fileChannel.transferFrom(source, 0, Long.MAX_VALUE);
+ downloadFlag = true;
+ } catch (final IOException e) {
+ Files.deleteIfExists(outFile);
+ }
+
+ if (!downloadFlag) {
+ URL githubUrl = new URL(buildInfo.githubUrl);
+ try (
+ final ReadableByteChannel source = Channels.newChannel(githubUrl.openStream());
+ final FileChannel fileChannel = FileChannel.open(outFile, CREATE, WRITE, TRUNCATE_EXISTING)
+ ) {
+ fileChannel.transferFrom(source, 0, Long.MAX_VALUE);
+ } catch (final IOException e) {
+ LeavesLogger.LOGGER.warning("Download " + buildInfo.fileName + " failed.");
+ Files.deleteIfExists(outFile);
+ updateTaskStarted = false;
+ e.printStackTrace();
+ return;
+ }
+ }
+
+ LeavesLogger.LOGGER.info("Download " + buildInfo.fileName + " completed.");
+ if (!isFileValid(outFile, buildInfo.sha256)) {
+ LeavesLogger.LOGGER.warning("Hash check failed for downloaded file " + buildInfo.fileName);
+ Files.deleteIfExists(outFile);
+ updateTaskStarted = false;
+ return;
+ }
+
+ File nowServerCore = new File(autoUpdateDir + File.separator + "leaves" + File.separator + buildInfo.fileName);
+ File backupServerCore = new File(autoUpdateDir + File.separator + "leaves" + File.separator + buildInfo.fileName + ".old");
+ Util.safeReplaceFile(nowServerCore, outFile.toFile(), backupServerCore);
+
+ try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(corePathFileName))) {
+ bufferedWriter.write(autoUpdateDir + File.separator + "leaves" + File.separator + "leaves-1.20.1.jar");
+ } catch (IOException e) {
+ e.printStackTrace();
+ updateTaskStarted = false;
+ return;
+ }
+
+ LeavesLogger.LOGGER.info("Leaves update completed, please restart your server.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ LeavesLogger.LOGGER.warning("Can't get build info, stopping update.");
+ }
+ updateTaskStarted = false;
+ }
+
+ private static boolean isFileValid(Path file, String hash) {
+ try (FileInputStream inputStream = new FileInputStream(file.toFile())) {
+ byte[] buffer = new byte[1024];
+ MessageDigest md5 = MessageDigest.getInstance("SHA-256");
+
+ for (int numRead; (numRead = inputStream.read(buffer)) > 0; ) {
+ md5.update(buffer, 0, numRead);
+ }
+
+ return toHexString(md5.digest()).equals(hash);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return false;
+ }
+
+ @NotNull
+ private static String toHexString(byte @NotNull [] bytes) {
+ StringBuilder builder = new StringBuilder();
+ for (byte b : bytes) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
+
+ private static LeavesBuildInfo getLatestBuildInfo(String mcVersion, String gitHash) {
+ try {
+ HttpURLConnection connection = (HttpURLConnection) new URL("https://api.leavesmc.top/projects/leaves/versions/" + mcVersion + "/builds/latest").openConnection();
+ connection.connect();
+ if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) return LeavesBuildInfo.NULL;
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), Charsets.UTF_8))) {
+ JsonObject obj = new Gson().fromJson(reader, JsonObject.class);
+ String channel = obj.get("channel").getAsString();
+
+ if (channel.equals("default")) {
+ int build = obj.get("build").getAsInt();
+
+ JsonArray changes = obj.get("changes").getAsJsonArray();
+ boolean needUpdate = true;
+ for (JsonElement change : changes) {
+ if (change.getAsJsonObject().get("commit").getAsString().startsWith(gitHash)) {
+ needUpdate = false;
+ break;
+ }
+ }
+
+ JsonObject downloadInfo = obj.get("downloads").getAsJsonObject().get("application").getAsJsonObject();
+ String name = downloadInfo.get("name").getAsString();
+ String sha256 = downloadInfo.get("sha256").getAsString();
+ String githubUrl = downloadInfo.get("url").getAsString();
+ String cdnUrl = downloadInfo.get("cdn_url").getAsString();
+ return new LeavesBuildInfo(build, name, sha256, needUpdate, cdnUrl, githubUrl);
+ } else {
+ return LeavesBuildInfo.NULL;
+ }
+ } catch (JsonSyntaxException | NumberFormatException e) {
+ e.printStackTrace();
+ return LeavesBuildInfo.NULL;
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ return LeavesBuildInfo.NULL;
+ }
+ }
+
+ private record LeavesBuildInfo(int build, String fileName, String sha256, boolean needUpdate, String cdnUrl, String githubUrl) {
+ public static LeavesBuildInfo NULL = new LeavesBuildInfo(-1, null, null, false, null, null);
+ }
+}