From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Kyle Wood <kyle@denwav.dev>
Date: Thu, 1 Mar 2018 19:37:52 -0600
Subject: [PATCH] Add version history to version command


diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
index 22a55be34fde453fedd987173d95b8b347a03588..9d687da5bdf398bb3f6c84cdf1249a7213d09f2e 100644
--- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java
@@ -7,6 +7,8 @@ import com.google.gson.*;
 import net.kyori.adventure.text.Component;
 import net.kyori.adventure.text.event.ClickEvent;
 import net.kyori.adventure.text.format.NamedTextColor;
+import net.kyori.adventure.text.format.TextDecoration;
+import net.kyori.adventure.text.TextComponent;
 
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
@@ -30,7 +32,10 @@ public class PaperVersionFetcher implements VersionFetcher {
     @Override
     public Component getVersionMessage(@Nonnull String serverVersion) {
         String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]");
-        return getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]);
+        final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]);
+        final Component history = getHistory();
+
+        return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage;
     }
 
     private static @Nullable String getMinecraftVersion() {
@@ -126,4 +131,19 @@ public class PaperVersionFetcher implements VersionFetcher {
             return -1;
         }
     }
+
+    @Nullable
+    private Component getHistory() {
+        final VersionHistoryManager.VersionData data = VersionHistoryManager.INSTANCE.getVersionData();
+        if (data == null) {
+            return null;
+        }
+
+        final String oldVersion = data.getOldVersion();
+        if (oldVersion == null) {
+            return null;
+        }
+
+        return Component.text("Previous version: " + oldVersion, NamedTextColor.GRAY, TextDecoration.ITALIC);
+    }
 }
diff --git a/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java
new file mode 100644
index 0000000000000000000000000000000000000000..660b2ec6b63a4ceffee44ab11f54dfa7c0d0996f
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/VersionHistoryManager.java
@@ -0,0 +1,153 @@
+package com.destroystokyo.paper;
+
+import com.google.common.base.MoreObjects;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import org.bukkit.Bukkit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+public enum VersionHistoryManager {
+    INSTANCE;
+
+    private final Gson gson = new Gson();
+
+    private final Logger logger = Bukkit.getLogger();
+
+    private VersionData currentData = null;
+
+    VersionHistoryManager() {
+        final Path path = Paths.get("version_history.json");
+
+        if (Files.exists(path)) {
+            // Basic file santiy checks
+            if (!Files.isRegularFile(path)) {
+                if (Files.isDirectory(path)) {
+                    logger.severe(path + " is a directory, cannot be used for version history");
+                } else {
+                    logger.severe(path + " is not a regular file, cannot be used for version history");
+                }
+                // We can't continue
+                return;
+            }
+
+            try (final BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
+                currentData = gson.fromJson(reader, VersionData.class);
+            } catch (final IOException e) {
+                logger.log(Level.SEVERE, "Failed to read version history file '" + path + "'", e);
+                return;
+            } catch (final JsonSyntaxException e) {
+                logger.log(Level.SEVERE, "Invalid json syntax for file '" + path + "'", e);
+                return;
+            }
+
+            final String version = Bukkit.getVersion();
+            if (version == null) {
+                logger.severe("Failed to retrieve current version");
+                return;
+            }
+
+            if (currentData == null) {
+                // Empty file
+                currentData = new VersionData();
+                currentData.setCurrentVersion(version);
+                writeFile(path);
+                return;
+            }
+
+            if (!version.equals(currentData.getCurrentVersion())) {
+                // The version appears to have changed
+                currentData.setOldVersion(currentData.getCurrentVersion());
+                currentData.setCurrentVersion(version);
+                writeFile(path);
+            }
+        } else {
+            // File doesn't exist, start fresh
+            currentData = new VersionData();
+            // oldVersion is null
+            currentData.setCurrentVersion(Bukkit.getVersion());
+            writeFile(path);
+        }
+    }
+
+    private void writeFile(@Nonnull final Path path) {
+        try (final BufferedWriter writer = Files.newBufferedWriter(
+            path,
+            StandardCharsets.UTF_8,
+            StandardOpenOption.WRITE,
+            StandardOpenOption.CREATE,
+            StandardOpenOption.TRUNCATE_EXISTING
+        )) {
+            gson.toJson(currentData, writer);
+        } catch (final IOException e) {
+            logger.log(Level.SEVERE, "Failed to write to version history file", e);
+        }
+    }
+
+    @Nullable
+    public VersionData getVersionData() {
+        return currentData;
+    }
+
+    public static class VersionData {
+        private String oldVersion;
+
+        private String currentVersion;
+
+        @Nullable
+        public String getOldVersion() {
+            return oldVersion;
+        }
+
+        public void setOldVersion(@Nullable String oldVersion) {
+            this.oldVersion = oldVersion;
+        }
+
+        @Nullable
+        public String getCurrentVersion() {
+            return currentVersion;
+        }
+
+        public void setCurrentVersion(@Nullable String currentVersion) {
+            this.currentVersion = currentVersion;
+        }
+
+        @Override
+        public String toString() {
+            return MoreObjects.toStringHelper(this)
+                .add("oldVersion", oldVersion)
+                .add("currentVersion", currentVersion)
+                .toString();
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            final VersionData versionData = (VersionData) o;
+            return Objects.equals(oldVersion, versionData.oldVersion) &&
+                Objects.equals(currentVersion, versionData.currentVersion);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(oldVersion, currentVersion);
+        }
+    }
+}
diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
index bbe2fabc9251838f232480a04f0a2cf2b49f2812..a6126515e497cb0769bdc96881389026196bbbae 100644
--- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java
@@ -214,6 +214,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface
         // Paper end - initialize global and world-defaults configuration
         io.papermc.paper.command.PaperCommands.registerCommands(this); // Paper - setup /paper command
         com.destroystokyo.paper.Metrics.PaperMetrics.startMetrics(); // Paper - start metrics
+        com.destroystokyo.paper.VersionHistoryManager.INSTANCE.getClass(); // Paper - load version history now
 
         this.setPvpAllowed(dedicatedserverproperties.pvp);
         this.setFlightAllowed(dedicatedserverproperties.allowFlight);
