Refactor updater
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
Minecon724 2025-01-20 08:10:38 +01:00
parent 2430416915
commit e5d2938845
Signed by: Minecon724
GPG key ID: 3CCC4D267742C8E8
10 changed files with 137 additions and 142 deletions

View file

@ -1,59 +0,0 @@
/*
* Copyright (C) 2024 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text.
*/
package eu.m724.tweaks.updater;
import eu.m724.tweaks.updater.cache.SpigotResource;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
public class PluginScanner {
private final Plugin thisPlugin;
PluginScanner(Plugin thisPlugin) {
this.thisPlugin = thisPlugin;
}
public Set<SpigotResource> load() throws IOException {
File installedPluginsYml = new File(thisPlugin.getDataFolder(), "installed_plugins.yml");
if (!installedPluginsYml.exists()) {
thisPlugin.saveResource("installed_plugins.yml", false);
}
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(installedPluginsYml);
Plugin[] plugins = thisPlugin.getServer().getPluginManager().getPlugins();
Set<SpigotResource> spigotResources = new HashSet<>();
for (Plugin plugin : plugins) {
String pluginName = plugin.getName();
if (!configuration.isSet(pluginName)) {
configuration.set(pluginName, -1);
continue;
}
int pluginId = configuration.getInt(pluginName);
if (pluginId > 0) {
spigotResources.add(
new SpigotResource(plugin, pluginId, pluginName)
);
}
}
configuration.save(installedPluginsYml);
return spigotResources;
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
@ -7,7 +7,8 @@
package eu.m724.tweaks.updater; package eu.m724.tweaks.updater;
import eu.m724.tweaks.Language; import eu.m724.tweaks.Language;
import eu.m724.tweaks.updater.cache.VersionedResource; import eu.m724.tweaks.updater.backend.UpdateChecker;
import eu.m724.tweaks.updater.object.VersionedResource;
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;
import net.md_5.bungee.api.chat.ClickEvent; import net.md_5.bungee.api.chat.ClickEvent;
@ -20,18 +21,25 @@ import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.awt.*; import java.awt.*;
import java.time.Instant;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
public class UpdaterCommands implements CommandExecutor { public class UpdaterCommands implements CommandExecutor {
private final UpdateChecker updateChecker;
public UpdaterCommands(UpdateChecker updateChecker) {
this.updateChecker = updateChecker;
}
@Override @Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) { public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (UpdateChecker.lastChecked == null) { var lastChecked = updateChecker.getLastChecked();
if (updateChecker.getLastChecked() == -1) {
sender.sendMessage(Language.getString("updatesNotChecked")); sender.sendMessage(Language.getString("updatesNotChecked"));
return true; return true;
} }
String lastChecked = UpdateChecker.lastChecked.format(DateTimeFormatter.ofPattern("HH:mm")); int n = updateChecker.getAvailableUpdates().size();
int n = UpdateChecker.availableUpdates.size();
if (n > 0) { if (n > 0) {
sender.spigot().sendMessage( sender.spigot().sendMessage(
@ -39,13 +47,14 @@ public class UpdaterCommands implements CommandExecutor {
); );
int i = 0; int i = 0;
for (VersionedResource v : UpdateChecker.availableUpdates) { for (VersionedResource v : updateChecker.getAvailableUpdates()) {
sender.spigot().sendMessage( sender.spigot().sendMessage(
new ComponentBuilder(++i + ". ").color(ChatColor.GRAY).build(), resourceToBaseComponent(v) new ComponentBuilder(++i + ". ").color(ChatColor.GRAY).build(), resourceToBaseComponent(v)
); );
} }
} else { } else {
sender.spigot().sendMessage(Language.getComponent("updatesNoUpdates", ChatColor.GREEN, lastChecked)); var lastCheckedFormat = DateTimeFormatter.ofPattern("HH:mm").format(Instant.ofEpochMilli(lastChecked));
sender.spigot().sendMessage(Language.getComponent("updatesNoUpdates", ChatColor.GREEN, lastCheckedFormat));
} }
return true; return true;

View file

@ -8,9 +8,13 @@ package eu.m724.tweaks.updater;
import eu.m724.tweaks.DebugLogger; import eu.m724.tweaks.DebugLogger;
import eu.m724.tweaks.TweaksModule; import eu.m724.tweaks.TweaksModule;
import eu.m724.tweaks.updater.cache.ResourceVersion; import eu.m724.tweaks.updater.backend.UpdateChecker;
import eu.m724.tweaks.updater.cache.SpigotResource; import eu.m724.tweaks.updater.backend.VersionCache;
import eu.m724.tweaks.updater.cache.VersionedResource; import eu.m724.tweaks.updater.object.ResourceVersion;
import eu.m724.tweaks.updater.object.SpigotResource;
import eu.m724.tweaks.updater.object.VersionedResource;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
@ -23,38 +27,72 @@ import java.util.stream.Collectors;
public class UpdaterModule extends TweaksModule { public class UpdaterModule extends TweaksModule {
@Override @Override
protected void onInit() { protected void onInit() {
// scan installed plugins var resources = loadInstalledPlugins();
Set<SpigotResource> resources;
try {
resources = new PluginScanner(getPlugin()).load();
} catch (IOException e) {
throw new RuntimeException("Loading plugins", e);
}
// load installed versions from cache // load installed versions from cache
var cacheFile = new File(getPlugin().getDataFolder(), "storage/cache/updater"); var cacheFile = new File(getPlugin().getDataFolder(), "storage/cache/updater");
Set<ResourceVersion> installedVersions; final var installedVersions = loadInstalledVersionCache(cacheFile);
var versionedResources = resources.stream()
.map(res -> new VersionedResource(
res, installedVersions.stream().filter(iv -> iv.resourceId() == res.resourceId()).findFirst().orElse(null), null
))
.collect(Collectors.toSet());
var updateChecker = new UpdateChecker(getPlugin().getLogger(), cacheFile, versionedResources);
updateChecker.runTaskTimerAsynchronously(getPlugin(), 600, 12 * 3600 * 20); // 12 hours
registerCommand("updates", new UpdaterCommands(updateChecker));
}
private Set<ResourceVersion> loadInstalledVersionCache(File cacheFile) {
try (FileInputStream inputStream = new FileInputStream(cacheFile)) { try (FileInputStream inputStream = new FileInputStream(cacheFile)) {
installedVersions = VersionCheckCache.loadAll(inputStream); return VersionCache.loadAll(inputStream);
} catch (FileNotFoundException e) { } catch (FileNotFoundException ignored) {
installedVersions = new HashSet<>();
} catch (IOException e) { } catch (IOException e) {
DebugLogger.warning("Error loading installed version cache, starting fresh. " + e.getMessage()); DebugLogger.warning("Error loading installed version cache, starting fresh. " + e.getMessage());
installedVersions = new HashSet<>();
} }
final Set<ResourceVersion> ivf = installedVersions; return new HashSet<>();
Set<VersionedResource> versionedResources = resources.stream() }
.map(res -> new VersionedResource(
res, ivf.stream().filter(iv -> iv.resourceId() == res.resourceId()).findFirst().orElse(null), null
)).collect(Collectors.toSet());
private Set<SpigotResource> loadInstalledPlugins() {
File installedPluginsYml = new File(getPlugin().getDataFolder(), "installed_plugins.yml");
new UpdateChecker(getPlugin().getLogger(), cacheFile, versionedResources) if (!installedPluginsYml.exists()) {
.runTaskTimerAsynchronously(getPlugin(), 600, 12 * 3600 * 20); // 12 hours getPlugin().saveResource("installed_plugins.yml", false);
}
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(installedPluginsYml);
Plugin[] plugins = getPlugin().getServer().getPluginManager().getPlugins();
Set<SpigotResource> spigotResources = new HashSet<>();
for (Plugin plugin : plugins) {
String pluginName = plugin.getName();
if (!configuration.isSet(pluginName)) {
configuration.set(pluginName, -1);
continue;
}
int pluginId = configuration.getInt(pluginName);
if (pluginId > 0) {
spigotResources.add(
new SpigotResource(plugin, pluginId, pluginName)
);
}
}
try {
configuration.save(installedPluginsYml);
} catch (IOException e) {
throw new RuntimeException("Failed to update installed_plugins.yml", e);
}
return spigotResources;
registerCommand("updates", new UpdaterCommands());
} }
} }

View file

@ -4,18 +4,16 @@
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater; package eu.m724.tweaks.updater.backend;
import eu.m724.tweaks.DebugLogger; import eu.m724.tweaks.DebugLogger;
import eu.m724.tweaks.Language; import eu.m724.tweaks.Language;
import eu.m724.tweaks.updater.cache.VersionedResource; import eu.m724.tweaks.updater.object.VersionedResource;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.time.LocalTime;
import java.time.ZoneOffset;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -28,10 +26,10 @@ public class UpdateChecker extends BukkitRunnable {
private final File cacheFile; private final File cacheFile;
private final Set<VersionedResource> resources; private final Set<VersionedResource> resources;
static final Set<VersionedResource> availableUpdates = new HashSet<>(); private final Set<VersionedResource> availableUpdates = new HashSet<>();
static LocalTime lastChecked = null; private long lastChecked = -1;
UpdateChecker(Logger logger, File cacheFile, Set<VersionedResource> resources) { public UpdateChecker(Logger logger, File cacheFile, Set<VersionedResource> resources) {
this.logger = logger; this.logger = logger;
this.cacheFile = cacheFile; this.cacheFile = cacheFile;
this.resources = resources; // TODO make a copy? this.resources = resources; // TODO make a copy?
@ -39,7 +37,7 @@ public class UpdateChecker extends BukkitRunnable {
private void checkAll() { private void checkAll() {
DebugLogger.fine("Checking for updates"); DebugLogger.fine("Checking for updates");
lastChecked = LocalTime.now(ZoneOffset.UTC); lastChecked = System.currentTimeMillis();
availableUpdates.clear(); availableUpdates.clear();
for (VersionedResource versionedResource : Set.copyOf(resources)) { for (VersionedResource versionedResource : Set.copyOf(resources)) {
@ -47,19 +45,21 @@ public class UpdateChecker extends BukkitRunnable {
int page = versionedResource.running() != null ? versionedResource.running().page() : 1; int page = versionedResource.running() != null ? versionedResource.running().page() : 1;
try { try {
VersionedResource newResource = new VersionFinder(versionedResource.resource(), page).join(); // this runs async so it's ok VersionedResource newResource = new VersionScanner(versionedResource.resource(), page).join(); // this runs async so it's ok
if (!versionedResource.equals(newResource)) { if (!versionedResource.equals(newResource)) {
resources.remove(versionedResource); resources.remove(versionedResource);
if (newResource.running() == null) { if (newResource.running() == null) {
logger.warning("Unable to find installed version of %s".formatted(pluginName)); DebugLogger.warning("Unable to find installed version of %s", pluginName);
if (versionedResource.running() != null) { if (versionedResource.running() != null) {
logger.warning("Did you downgrade %s? If so, clear cache".formatted(pluginName)); DebugLogger.warning("Did you downgrade %s? If so, clear cache", pluginName);
} }
} else { } else {
if (!newResource.running().equals(newResource.latest())) { if (!newResource.running().equals(newResource.latest())) {
availableUpdates.add(newResource); availableUpdates.add(newResource);
} }
} }
resources.add(newResource); resources.add(newResource);
} }
} catch (CompletionException e) { } catch (CompletionException e) {
@ -85,11 +85,19 @@ public class UpdateChecker extends BukkitRunnable {
DebugLogger.finer("Done checking, now saving"); DebugLogger.finer("Done checking, now saving");
cacheFile.getParentFile().mkdirs(); cacheFile.getParentFile().mkdirs();
try (FileOutputStream outputStream = new FileOutputStream(cacheFile)) { try (FileOutputStream outputStream = new FileOutputStream(cacheFile)) {
VersionCheckCache.writeAll(outputStream, resources.stream().map(VersionedResource::running).filter(Objects::nonNull).collect(Collectors.toSet())); VersionCache.writeAll(outputStream, resources.stream().map(VersionedResource::running).filter(Objects::nonNull).collect(Collectors.toSet()));
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
alert(); alert();
} }
public long getLastChecked() {
return lastChecked;
}
public Set<VersionedResource> getAvailableUpdates() {
return availableUpdates;
}
} }

View file

@ -1,12 +1,12 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater; package eu.m724.tweaks.updater.backend;
import eu.m724.tweaks.updater.cache.ResourceVersion; import eu.m724.tweaks.updater.object.ResourceVersion;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
@ -15,7 +15,7 @@ import java.io.OutputStream;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
public class VersionCheckCache { public class VersionCache {
private static final byte FILE_VERSION = 1; private static final byte FILE_VERSION = 1;
public static Set<ResourceVersion> loadAll(FileInputStream inputStream) throws IOException { public static Set<ResourceVersion> loadAll(FileInputStream inputStream) throws IOException {

View file

@ -1,19 +1,20 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater; package eu.m724.tweaks.updater.backend;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import eu.m724.tweaks.updater.cache.SpigotResource; import eu.m724.tweaks.DebugLogger;
import eu.m724.tweaks.updater.cache.ResourceVersion; import eu.m724.tweaks.updater.object.SpigotResource;
import eu.m724.tweaks.updater.cache.UpdateDescription; import eu.m724.tweaks.updater.object.ResourceVersion;
import eu.m724.tweaks.updater.cache.VersionedResource; import eu.m724.tweaks.updater.object.UpdateDescription;
import eu.m724.tweaks.updater.object.VersionedResource;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -26,23 +27,25 @@ import java.util.concurrent.Executors;
// TODO optimize // TODO optimize
public class VersionFinder extends CompletableFuture<VersionedResource> { public class VersionScanner extends CompletableFuture<VersionedResource> {
private final SpigotResource resource; private final SpigotResource resource;
private final int fromPage; private final int fromPage;
VersionFinder(SpigotResource resource, int fromPage) { VersionScanner(SpigotResource resource, int fromPage) {
this.resource = resource; this.resource = resource;
this.fromPage = fromPage; this.fromPage = fromPage;
start(); start();
} }
VersionFinder(SpigotResource resource) { VersionScanner(SpigotResource resource) {
this(resource, 1); this(resource, 1);
} }
private void start() { private void start() {
//System.out.printf("STarting for %d %s\n", resource.resourceId(), resource.plugin().getName()); //System.out.printf("STarting for %d %s\n", resource.resourceId(), resource.plugin().getName());
DebugLogger.finer("Scanning %s (#%d) from page %d", resource.name(), resource.resourceId(), fromPage);
try (ExecutorService executor = Executors.newSingleThreadExecutor()) { try (ExecutorService executor = Executors.newSingleThreadExecutor()) {
executor.execute(() -> { executor.execute(() -> {
try { try {
@ -51,13 +54,14 @@ public class VersionFinder extends CompletableFuture<VersionedResource> {
int page; int page;
for (page = fromPage; page < 1000; page++) { for (page = fromPage; page < 1000; page++) {
//System.out.println("Page " + page); DebugLogger.finer("Scan %s now at page %d", resource.name(), fromPage);
String url = "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdates&page=%d&id=%d".formatted(page, resource.resourceId()); String url = "https://api.spigotmc.org/simple/0.2/index.php?action=getResourceUpdates&page=%d&id=%d".formatted(page, resource.resourceId());
HttpRequest request; HttpRequest request;
try { try {
request = HttpRequest.newBuilder(new URI(url)) request = HttpRequest.newBuilder(new URI(url))
.header("User-Agent", "twu/1") .header("User-Agent", "twu/1") // tweaks updater v1
.build(); .build();
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@ -68,6 +72,7 @@ public class VersionFinder extends CompletableFuture<VersionedResource> {
String body = response.body(); String body = response.body();
if (body.isBlank()) { if (body.isBlank()) {
DebugLogger.finer("Body is blank, stopping");
page--; page--;
break; break;
} }
@ -75,15 +80,7 @@ public class VersionFinder extends CompletableFuture<VersionedResource> {
JsonArray jsonArray = JsonParser.parseString(body).getAsJsonArray(); JsonArray jsonArray = JsonParser.parseString(body).getAsJsonArray();
for (JsonElement ele : jsonArray) { for (JsonElement ele : jsonArray) {
JsonObject versionJson = ele.getAsJsonObject(); JsonObject versionJson = ele.getAsJsonObject();
if (isRunningVersion(versionJson)) {
runningVersion = new ResourceVersion(
resource.resourceId(),
page,
versionJson.get("id").getAsInt(),
versionJson.get("resource_version").getAsString(),
null // no need for changelog of running version
);
}
latestVersion = new ResourceVersion( latestVersion = new ResourceVersion(
resource.resourceId(), resource.resourceId(),
page, page,
@ -94,14 +91,17 @@ public class VersionFinder extends CompletableFuture<VersionedResource> {
versionJson.get("message").getAsString() versionJson.get("message").getAsString()
) )
); );
//System.out.printf("%d %d %s\n", page, versionJson.get("id").getAsInt(), versionJson.get("resource_version").getAsString());
if (isRunningVersion(versionJson))
runningVersion = latestVersion;
DebugLogger.finer("%s - %s #%d", resource.name(), latestVersion.updateId(), latestVersion.updateId());
} }
if (jsonArray.size() < 10) break; if (jsonArray.size() < 10) break;
} }
} }
//System.out.println("Done");
if (page > 999) { if (page > 999) {
throw new Exception("Too many pages"); throw new Exception("Too many pages");
@ -117,7 +117,8 @@ public class VersionFinder extends CompletableFuture<VersionedResource> {
} }
private boolean isRunningVersion(JsonObject versionJson) { private boolean isRunningVersion(JsonObject versionJson) {
// TODO // TODO make it work with more advanced strings
return versionJson.get("resource_version").getAsString().equals(resource.plugin().getDescription().getVersion()); return versionJson.get("resource_version").getAsString()
.equals(resource.plugin().getDescription().getVersion());
} }
} }

View file

@ -1,10 +1,10 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater.cache; package eu.m724.tweaks.updater.object;
import java.util.Objects; import java.util.Objects;

View file

@ -1,10 +1,10 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater.cache; package eu.m724.tweaks.updater.object;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
@ -12,5 +12,4 @@ public record SpigotResource(
Plugin plugin, Plugin plugin,
int resourceId, int resourceId,
String name String name
) { ) { }
}

View file

@ -1,13 +1,12 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater.cache; package eu.m724.tweaks.updater.object;
public record UpdateDescription( public record UpdateDescription(
String title, String title,
String description String description
) { ) { }
}

View file

@ -1,10 +1,10 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
package eu.m724.tweaks.updater.cache; package eu.m724.tweaks.updater.object;
public record VersionedResource( public record VersionedResource(
SpigotResource resource, SpigotResource resource,