Compare commits

23 Commits
2.0 ... main

Author SHA1 Message Date
ae39780bbb README.md aktualisiert 2026-03-10 07:18:20 +00:00
48d74743d9 LICENSE gelöscht 2026-03-01 11:12:07 +00:00
b40a5f6280 README.md aktualisiert 2026-03-01 11:11:58 +00:00
33dfc413e3 Delete Test/test.txt via Git Manager GUI 2026-02-28 20:04:18 +00:00
badbc57202 Test/test.txt hinzugefügt 2026-02-28 20:01:50 +00:00
d31d5031ff Delete test.txt via Git Manager GUI 2026-02-28 20:01:13 +00:00
1617a4d4ab test.txt hinzugefügt 2026-02-28 19:58:32 +00:00
68bad3f09b README.md aktualisiert 2026-02-28 19:47:31 +00:00
b9a328564f target/maven-archiver/pom.properties gelöscht 2026-02-28 19:47:03 +00:00
14115b0083 target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst gelöscht 2026-02-28 19:46:57 +00:00
5d099b2db8 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst gelöscht 2026-02-28 19:46:53 +00:00
71c1892c09 target/classes/de/viper/autoworldreset/scheduler/ResetScheduler.class gelöscht 2026-02-28 19:46:01 +00:00
05b98947fb target/classes/de/viper/autoworldreset/util/CronParserUtil.class gelöscht 2026-02-28 19:45:55 +00:00
9d0c1a0a11 target/classes/de/viper/autoworldreset/AutoWorldReset.class gelöscht 2026-02-28 19:45:48 +00:00
875ea8aa2b target/classes/de/viper/autoworldreset/AutoWorldReset$UpdateNotifyListener.class gelöscht 2026-02-28 19:45:44 +00:00
419c0460f1 target/classes/de/viper/autoworldreset/ResetManager.class gelöscht 2026-02-28 19:45:40 +00:00
4ad261a91a target/classes/config.yml gelöscht 2026-02-28 19:45:33 +00:00
6a503e5c11 target/classes/plugin.yml gelöscht 2026-02-28 19:45:21 +00:00
a71a7c358a target/AutoWorldReset.jar gelöscht 2026-02-28 19:45:13 +00:00
48bdbe7118 target/original-AutoWorldReset-1.2.jar gelöscht 2026-02-28 19:45:08 +00:00
a73ecc7199 Upload pom.xml via GUI 2026-02-28 19:44:46 +00:00
9300af4978 Update from Git Manager GUI 2026-02-28 20:44:44 +01:00
8a702488be Update from Git Manager GUI 2026-02-28 20:44:41 +01:00
358 changed files with 852 additions and 1619 deletions

18
LICENSE
View File

@@ -1,18 +0,0 @@
MIT License
Copyright (c) 2025 M_Viper
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,4 +1,4 @@
# ServerRestorer # AutoWorldReset
Ein Bukkit-Plugin zum Sichern Ihres Servers. Ein Bukkit-Plugin zum Sichern Ihres Servers.
@@ -51,10 +51,10 @@ Da der Server angehalten werden muss, um Änderungen an den Dateien vorzunehmen,
## Befehle ## Befehle
- `/sr save` Erzwingt das Speichern - `/awr save` Erzwingt das Speichern
- `/sr restore <backup>` Stellt den Server in einen früheren Zustand wieder her - `/awr restore <backup>` Stellt den Server in einen früheren Zustand wieder her
- `/sr enableAutoSaver [Verzögerung]` Ändert die automatische Speicherverzögerung - `/awr enableAutoSaver [Verzögerung]` Ändert die automatische Speicherverzögerung
- `/sr disableAutoSaver` Deaktiviert den Autosaver - `/awr disableAutoSaver` Deaktiviert den Autosaver
--- ---
@@ -63,3 +63,9 @@ Da der Server angehalten werden muss, um Änderungen an den Dateien vorzunehmen,
- `serverrestorer.*` Enthält alle unten aufgeführten Berechtigungen - `serverrestorer.*` Enthält alle unten aufgeführten Berechtigungen
- `serverrestorer.save` Ermöglicht Zugriff auf alle `/sr save`-Befehle - `serverrestorer.save` Ermöglicht Zugriff auf alle `/sr save`-Befehle
- `serverrestorer.restore` Ermöglicht Zugriff auf alle `/sr restore`-Befehle - `serverrestorer.restore` Ermöglicht Zugriff auf alle `/sr restore`-Befehle
---
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.

163
pom.xml
View File

@@ -1,13 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 xsi:schemaLocation="
http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd"> http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>me.viper</groupId> <modelVersion>4.0.0</modelVersion>
<artifactId>ServerRestorer-Reborn</artifactId> <groupId>de.viper</groupId>
<version>2.0</version> <artifactId>AutoWorldReset</artifactId>
<name>ServerRestorer</name> <version>1.2</version>
<name>AutoWorldReset</name>
<description>Minecraft Plugin für automatisches Welt-Reset mit Multiverse-Core und Cron-Scheduler</description>
<repositories> <repositories>
<repository> <repository>
@@ -16,26 +18,101 @@
</repository> </repository>
</repositories> </repositories>
<properties> <dependencies>
<java.version>17</java.version> <!-- Spigot API -->
<spigot.version>1.21.4-R0.1-SNAPSHOT</spigot.version> <dependency>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <groupId>org.spigotmc</groupId>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <artifactId>spigot-api</artifactId>
</properties> <version>1.21.5-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Cron-Utils für Cron Parsing -->
<dependency>
<groupId>com.cronutils</groupId>
<artifactId>cron-utils</artifactId>
<version>9.2.0</version>
</dependency>
<!-- Multiverse-Core (lokal installiert) -->
<dependency>
<groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId>
<version>5.1.2</version>
<scope>provided</scope>
</dependency>
<!-- bStats Bukkit Dependency -->
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.1.0</version>
<scope>compile</scope>
</dependency>
<!-- org.json: für JSON Parsing (UpdateChecker) -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20240303</version>
</dependency>
</dependencies>
<build> <build>
<finalName>${project.artifactId}</finalName> <plugins>
<defaultGoal>install</defaultGoal> <!-- Maven Compiler Plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
<!-- Maven Shade Plugin mit Relocation für bStats -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.0</version>
<executions>
<execution>
<phase>package</phase>
<goals><goal>shade</goal></goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>org.bstats</pattern>
<!-- Hier deinen Package-Namespace anpassen, z.B. de.viper.shaded.bstats -->
<shadedPattern>de.viper.shaded.bstats</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>de.viper.autoworldreset.AutoWorldReset</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources> <resources>
<resource> <resource>
<directory>src/main/resources</directory> <directory>src/main/resources</directory>
<excludes>
<exclude>**/*.java</exclude>
</excludes>
</resource>
<resource>
<directory>${basedir}/src/main/resources</directory>
<filtering>true</filtering> <filtering>true</filtering>
<includes> <includes>
<include>plugin.yml</include> <include>plugin.yml</include>
@@ -43,52 +120,6 @@
</includes> </includes>
</resource> </resource>
</resources> </resources>
<plugins>
<!-- Compiler plugin -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
</plugins>
<sourceDirectory>src/main/java</sourceDirectory>
</build> </build>
<dependencies>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>${spigot.version}</version>
<scope>provided</scope>
</dependency>
<!-- Commons Net -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
<!-- JSch for SSH -->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<!-- JSON Simple -->
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1.1</version>
</dependency>
</dependencies>
</project> </project>

View File

@@ -0,0 +1,316 @@
package de.viper.autoworldreset;
import de.viper.autoworldreset.scheduler.ResetScheduler;
import org.bstats.bukkit.Metrics;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Scanner;
import java.util.function.Consumer;
import org.json.JSONObject;
public class AutoWorldReset extends JavaPlugin {
private ResetManager resetManager;
private ResetScheduler scheduler;
// BUG FIX #9: lang.yml wird jetzt als eigene FileConfiguration geladen und
// tatsächlich verwendet. Vorher wurde die Datei zwar gespeichert, aber
// Nachrichten wurden immer aus config.yml gelesen lang.yml war toter Code.
private FileConfiguration langConfig;
private Metrics metrics;
private String latestVersion = null;
private static final int RESOURCE_ID = 127822;
private static final int BSTATS_PLUGIN_ID = 26874;
@Override
public void onEnable() {
// BUG FIX #10: registerEvents(this, this) wurde entfernt.
// Die Hauptklasse implementierte kein Listener-Interface und hatte keine
// @EventHandler-Methoden mehr das war also eine sinnlose Registrierung,
// die zu einer misleadenden Warnung führen kann.
getServer().getPluginManager().registerEvents(new UpdateNotifyListener(), this);
saveDefaultConfig();
loadLangConfig(); // lang.yml laden
resetManager = new ResetManager(this);
scheduler = new ResetScheduler(this, resetManager);
boolean autoReset = getConfig().getBoolean("auto-reset-on-startup", false);
if (autoReset) {
Bukkit.getScheduler().runTaskLater(this, () -> {
String worldName = getConfig().getString("world-name");
// BUG FIX #11: Null-Check für worldName beim Startup-Reset.
if (worldName == null || worldName.isEmpty()) {
getLogger().warning("auto-reset-on-startup ist aktiv, aber 'world-name' ist nicht konfiguriert!");
return;
}
boolean success = resetManager.restoreBackup(worldName);
if (success) {
getLogger().info("Backup beim Serverstart erfolgreich wiederhergestellt.");
} else {
getLogger().warning("Backup konnte beim Serverstart nicht wiederhergestellt werden.");
}
}, 20L * 10);
}
if (getConfig().getBoolean("scheduler.enabled")) {
scheduler.start();
}
metrics = new Metrics(this, BSTATS_PLUGIN_ID);
getLogger().info("bStats initialisiert.");
getLatestVersion(latest -> {
if (latest == null || latest.isEmpty()) return; // BUG FIX #12: kein weiterer Code bei leerem Ergebnis
String current = getDescription().getVersion();
String normalizedLatest = latest.replaceFirst("(?i)^v\\.?\\s*", "").trim();
String normalizedCurrent = current.replaceFirst("(?i)^v\\.?\\s*", "").trim();
if (isNewerVersion(normalizedLatest, normalizedCurrent)) {
latestVersion = latest;
getLogger().info("Neue Version verfügbar: " + latest + " (aktuell: " + current + ")");
getLogger().info("Download: https://www.spigotmc.org/resources/" + RESOURCE_ID + "/");
// Bukkit-API-Calls müssen auf dem Hauptthread laufen!
// BUG FIX #13: Spielerbenachrichtigung in runTask() verschoben,
// da dieser Code in einem Async-Thread läuft (getServer().getScheduler()
// .runTaskAsynchronously) und Bukkit-API nicht thread-safe ist.
Bukkit.getScheduler().runTask(this, () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
if (player.isOp()) {
notifyUpdateToPlayer(player, latest, current);
}
}
});
}
});
getLogger().info("AutoWorldReset wurde aktiviert.");
}
@Override
public void onDisable() {
if (scheduler != null) {
scheduler.stop();
}
getLogger().info("AutoWorldReset wurde deaktiviert.");
}
// -----------------------------------------------------------------------
// lang.yml Handling
// -----------------------------------------------------------------------
private void loadLangConfig() {
File langFile = new File(getDataFolder(), "lang.yml");
if (!langFile.exists()) {
// lang.yml ist nicht in der JAR eingebettet → manuell mit Standardwerten anlegen
getDataFolder().mkdirs();
YamlConfiguration defaults = new YamlConfiguration();
defaults.set("messages.resetting", "&eDie Welt wird zurückgesetzt...");
defaults.set("messages.finished", "&aWelt wurde erfolgreich zurückgesetzt!");
defaults.set("messages.no_permission", "&cDu hast keine Berechtigung, diesen Befehl auszuführen.");
defaults.set("messages.invalid_command", "&cUngültiger Befehl oder Argument.");
defaults.set("messages.kick-message", "&cDie Welt wird zurückgesetzt, du wurdest gekickt.");
defaults.set("messages.teleport-message","&cDie Welt wird zurückgesetzt. Du wurdest sicher teleportiert.");
try {
defaults.save(langFile);
getLogger().info("lang.yml wurde automatisch erstellt.");
} catch (IOException e) {
getLogger().warning("lang.yml konnte nicht erstellt werden: " + e.getMessage());
}
}
langConfig = YamlConfiguration.loadConfiguration(langFile);
}
/**
* Liest eine Nachricht aus lang.yml. Fällt auf config.yml und dann auf den
* Standardwert zurück, wenn der Schlüssel nicht gefunden wird.
*/
public String getLangMessage(String key, String defaultValue) {
if (langConfig != null && langConfig.contains("messages." + key)) {
return langConfig.getString("messages." + key, defaultValue);
}
return getConfig().getString("messages." + key, defaultValue);
}
// -----------------------------------------------------------------------
// Update-Checker
// -----------------------------------------------------------------------
private void getLatestVersion(Consumer<String> consumer) {
getServer().getScheduler().runTaskAsynchronously(this, () -> {
try {
HttpURLConnection connection = (HttpURLConnection)
new URL("https://api.spiget.org/v2/resources/" + RESOURCE_ID + "/versions/latest")
.openConnection();
connection.setRequestMethod("GET");
connection.addRequestProperty("User-Agent", "AutoWorldReset-UpdateChecker/1.0");
connection.setConnectTimeout(5000);
connection.setReadTimeout(5000);
try (Scanner scanner = new Scanner(connection.getInputStream())) {
String response = scanner.useDelimiter("\\A").next();
JSONObject json = new JSONObject(response);
String versionName = json.optString("name", "").trim();
consumer.accept(versionName);
}
} catch (Exception e) {
getLogger().warning("Update-Check fehlgeschlagen: " + e.getMessage());
consumer.accept("");
}
});
}
private boolean isNewerVersion(String latest, String current) {
try {
String[] latestParts = latest.split("\\.");
String[] currentParts = current.split("\\.");
int length = Math.max(latestParts.length, currentParts.length);
for (int i = 0; i < length; i++) {
int latestPart = (i < latestParts.length) ? Integer.parseInt(latestParts[i]) : 0;
int currentPart = (i < currentParts.length) ? Integer.parseInt(currentParts[i]) : 0;
if (latestPart > currentPart) return true;
if (latestPart < currentPart) return false;
}
return false;
} catch (NumberFormatException e) {
return !latest.equalsIgnoreCase(current);
}
}
private void notifyUpdateToPlayer(Player player, String latest, String current) {
player.sendMessage("§aEine neue Version von §e" + getDescription().getName()
+ " §aist verfügbar: §e" + latest + " §7(aktuell: " + current + ")");
player.sendMessage("§eDownload: §bhttps://www.spigotmc.org/resources/" + RESOURCE_ID + "/");
}
// -----------------------------------------------------------------------
// Inner class: Update-Benachrichtigung beim Join
// -----------------------------------------------------------------------
public class UpdateNotifyListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
if (player.isOp() && latestVersion != null) {
notifyUpdateToPlayer(player, latestVersion, getDescription().getVersion());
}
}
}
// -----------------------------------------------------------------------
// Commands
// -----------------------------------------------------------------------
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("autoworldreset.use")) {
sender.sendMessage(color(getLangMessage("no_permission", "&cDu hast keine Berechtigung.")));
return true;
}
if (!command.getName().equalsIgnoreCase("autoworldreset")) {
return false;
}
if (args.length == 0) {
sender.sendMessage("§eBenutze: /autoworldreset <reset|backup|restore|reload|start|stop|status>");
return true;
}
switch (args[0].toLowerCase()) {
case "reset":
sender.sendMessage(color(getLangMessage("resetting", "&eDie Welt wird zurückgesetzt...")));
Bukkit.getScheduler().runTask(this, () -> resetManager.resetWorld());
break;
case "backup":
sender.sendMessage("§eBackup wird erstellt...");
Bukkit.getScheduler().runTask(this, () -> {
boolean success = resetManager.createBackup();
sender.sendMessage(success
? "§aBackup erfolgreich erstellt."
: "§cBackup konnte nicht erstellt werden. Siehe Konsole für Details.");
});
break;
case "restore":
sender.sendMessage("§eBackup wird wiederhergestellt...");
Bukkit.getScheduler().runTask(this, () -> {
String worldName = getConfig().getString("world-name");
boolean success = resetManager.restoreBackup(worldName);
sender.sendMessage(success
? "§aBackup erfolgreich wiederhergestellt."
: "§cBackup konnte nicht wiederhergestellt werden. Siehe Konsole für Details.");
});
break;
case "reload":
reloadConfig();
loadLangConfig();
resetManager = new ResetManager(this);
if (scheduler != null) scheduler.stop();
scheduler = new ResetScheduler(this, resetManager);
if (getConfig().getBoolean("scheduler.enabled")) {
scheduler.start();
}
sender.sendMessage("§aKonfiguration erfolgreich neu geladen.");
getLogger().info(sender.getName() + " hat die Konfiguration neu geladen.");
break;
case "start":
if (scheduler.isRunning()) {
sender.sendMessage("§eScheduler läuft bereits.");
} else {
scheduler.start();
sender.sendMessage("§aScheduler gestartet.");
}
break;
case "stop":
scheduler.stop();
sender.sendMessage("§cScheduler gestoppt.");
break;
// BUG FIX #14: Neuer "status"-Befehl zeigt, ob der Scheduler läuft.
case "status":
sender.sendMessage("§eScheduler: " + (scheduler.isRunning() ? "§aAktiv" : "§cInaktiv"));
sender.sendMessage("§eWelt: §f" + getConfig().getString("world-name", "nicht konfiguriert"));
sender.sendMessage("§eCron: §f" + getConfig().getString("scheduler.cron", "-"));
break;
default:
sender.sendMessage(color(getLangMessage("invalid_command", "&cUngültiger Befehl oder Argument.")));
break;
}
return true;
}
public ResetManager getResetManager() {
return resetManager;
}
private String color(String msg) {
return ChatColor.translateAlternateColorCodes('&', msg);
}
}

View File

@@ -0,0 +1,204 @@
package de.viper.autoworldreset;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.WorldCreator;
import org.bukkit.entity.Player;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List;
public class ResetManager {
private final AutoWorldReset plugin;
public ResetManager(AutoWorldReset plugin) {
this.plugin = plugin;
}
public void resetWorld() {
String worldName = plugin.getConfig().getString("world-name");
if (worldName == null || worldName.isEmpty()) {
plugin.getLogger().warning("Keine Welt in der Config angegeben (key: world-name).");
return;
}
// BUG FIX #6: Die Backup-Prüfung wurde aus resetWorld() entfernt und korrekt
// platziert. Zuvor wurde backup.enabled geprüft, um einen RESTORE abzubrechen
// das ist logisch falsch. Das Flag sollte nur das automatische Erstellen eines
// Backups steuern, nicht das Wiederherstellen. Ein Reset setzt immer das Backup
// zurück; wenn keins da ist, schlägt restoreBackup() mit einer Warnung fehl.
boolean success = restoreBackup(worldName);
if (!success) {
plugin.getLogger().warning("Reset fehlgeschlagen: Backup konnte nicht wiederhergestellt werden.");
}
}
public boolean createBackup() {
String worldName = plugin.getConfig().getString("world-name");
if (worldName == null || worldName.isEmpty()) {
plugin.getLogger().warning("Kein Weltname konfiguriert (key: world-name).");
return false;
}
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
File backupFolder = new File(Bukkit.getWorldContainer(),
worldName + "_" + plugin.getConfig().getString("backup.folder-name", "backup"));
if (!worldFolder.exists()) {
plugin.getLogger().warning("Weltordner nicht gefunden: " + worldFolder.getAbsolutePath());
return false;
}
try {
World world = Bukkit.getWorld(worldName);
if (world != null) {
handlePlayers(world);
world.save();
}
if (backupFolder.exists()) {
deleteFolder(backupFolder.toPath());
}
// BUG FIX #7: copyFolder wirft jetzt korrekt eine IOException nach oben.
// Vorher wurden Fehler im Stream-Lambda stillschweigend geschluckt, sodass
// ein unvollständiges Backup als "erfolgreich" gemeldet wurde.
copyFolder(worldFolder.toPath(), backupFolder.toPath());
plugin.getLogger().info("Backup der Welt '" + worldName + "' erfolgreich erstellt.");
return true;
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Erstellen des Backups: " + e.getMessage());
e.printStackTrace();
return false;
}
}
public boolean restoreBackup(String worldName) {
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
File backupFolder = new File(Bukkit.getWorldContainer(),
worldName + "_" + plugin.getConfig().getString("backup.folder-name", "backup"));
if (!backupFolder.exists()) {
plugin.getLogger().warning("Backup-Ordner existiert nicht: " + backupFolder.getAbsolutePath()
+ ". Bitte zuerst ein Backup mit '/awr backup' erstellen.");
return false;
}
World world = Bukkit.getWorld(worldName);
if (world != null) {
handlePlayers(world);
world.save();
if (!Bukkit.unloadWorld(world, false)) {
plugin.getLogger().warning("Welt konnte nicht entladen werden: " + worldName);
return false;
}
}
try {
if (worldFolder.exists()) {
deleteFolder(worldFolder.toPath());
}
copyFolder(backupFolder.toPath(), worldFolder.toPath());
// BUG FIX #8: Rückgabewert von createWorld() prüfen.
// Früher wurde immer true zurückgegeben, auch wenn die Welt nicht
// geladen werden konnte (createWorld gibt null zurück bei Fehler).
World newWorld = Bukkit.createWorld(new WorldCreator(worldName));
if (newWorld == null) {
plugin.getLogger().severe("Welt '" + worldName + "' konnte nach Restore nicht geladen werden!");
return false;
}
plugin.getLogger().info("Backup der Welt '" + worldName + "' erfolgreich wiederhergestellt.");
return true;
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Wiederherstellen: " + e.getMessage());
e.printStackTrace();
return false;
}
}
private void handlePlayers(World world) {
String mode = plugin.getConfig().getString("handle-players", "TELEPORT").toUpperCase();
World fallback = Bukkit.getWorlds().get(0);
for (Player player : world.getPlayers()) {
switch (mode) {
case "KICK":
// Nachricht aus lang.yml lesen statt hardcoded
String kickMsg = plugin.getLangMessage("kick-message", "&cDie Welt wird zurückgesetzt.");
player.kickPlayer(org.bukkit.ChatColor.translateAlternateColorCodes('&', kickMsg));
break;
case "TELEPORT":
player.teleport(fallback.getSpawnLocation());
player.sendMessage(org.bukkit.ChatColor.translateAlternateColorCodes('&',
plugin.getLangMessage("teleport-message",
"&cDie Welt wird zurückgesetzt. Du wurdest sicher teleportiert.")));
break;
case "TELEPORT_BACK":
player.teleport(fallback.getSpawnLocation());
player.sendMessage(org.bukkit.ChatColor.translateAlternateColorCodes('&',
plugin.getLangMessage("teleport-message",
"&cDie Welt wird zurückgesetzt. Du wurdest vorübergehend teleportiert.")));
break;
default:
plugin.getLogger().warning("Unbekannter handle-players Modus: '" + mode + "'. Verwende TELEPORT.");
player.teleport(fallback.getSpawnLocation());
break;
}
}
}
private void deleteFolder(Path path) throws IOException {
if (Files.notExists(path)) return;
if (Files.isDirectory(path)) {
try (var entries = Files.newDirectoryStream(path)) {
for (var entry : entries) {
deleteFolder(entry);
}
}
}
Files.delete(path);
}
// Dateien, die vom laufenden Server gesperrt sind oder im Backup keinen
// Nutzen haben, werden beim Kopieren automatisch uebersprungen.
private static final java.util.Set<String> SKIP_FILES = java.util.Set.of(
"session.lock" // Vom Minecraft-Prozess exklusiv gesperrt
);
private void copyFolder(Path source, Path target) throws IOException {
List<String> errors = new ArrayList<>();
Files.walk(source).forEach(src -> {
String fileName = src.getFileName() != null ? src.getFileName().toString() : "";
if (SKIP_FILES.contains(fileName)) {
plugin.getLogger().info("Ueberspringe gesperrte Datei beim Backup: " + fileName);
return;
}
try {
Path dest = target.resolve(source.relativize(src));
if (Files.isDirectory(src)) {
if (!Files.exists(dest)) Files.createDirectories(dest);
} else {
Files.copy(src, dest, StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
errors.add(src + "" + e.getMessage());
}
});
if (!errors.isEmpty()) {
throw new IOException("Fehler beim Kopieren folgender Dateien:\n" + String.join("\n", errors));
}
}
}

View File

@@ -0,0 +1,85 @@
package de.viper.autoworldreset.scheduler;
import de.viper.autoworldreset.AutoWorldReset;
import de.viper.autoworldreset.ResetManager;
import de.viper.autoworldreset.util.CronParserUtil;
import org.bukkit.Bukkit;
import org.bukkit.scheduler.BukkitTask;
import java.time.Duration;
public class ResetScheduler {
private final AutoWorldReset plugin;
private final ResetManager resetManager;
private BukkitTask task;
// BUG FIX #4: Fehlende stopped-Flag hinzugefügt.
// Ohne dieses Flag rief der Callback nach Ausführung scheduleNextReset()
// erneut auf, selbst nachdem stop() bereits aufgerufen wurde. Das führte
// dazu, dass der Scheduler nach einem stop() ungewollt weiter lief.
private boolean stopped = false;
public ResetScheduler(AutoWorldReset plugin, ResetManager resetManager) {
this.plugin = plugin;
this.resetManager = resetManager;
}
public void start() {
stopped = false;
scheduleNextReset();
}
private void scheduleNextReset() {
if (stopped) return;
String cron = plugin.getConfig().getString("scheduler.cron");
// Null-Check (wird auch in CronParserUtil geprüft, aber defensive
// Programmierung ist hier sinnvoll für eine klare Log-Meldung)
if (cron == null || cron.isBlank()) {
plugin.getLogger().warning("Kein Cron-Ausdruck unter 'scheduler.cron' konfiguriert!");
return;
}
Duration delay = CronParserUtil.parseCronToDelay(cron);
if (delay == null) {
plugin.getLogger().warning("Ungültiger Cron-Ausdruck: '" + cron + "'. Scheduler wird nicht gestartet.");
return;
}
// BUG FIX #5: Delay von 0 oder negativ abfangen.
// Wenn die nächste Ausführungszeit in der Vergangenheit liegt (z. B. wegen
// Systemzeitproblemen), würde ein Delay von 0 Ticks zu einem Sofort-Reset führen.
if (delay.isZero() || delay.isNegative()) {
plugin.getLogger().warning("Berechneter Delay ist 0 oder negativ Reset wird übersprungen, nächsten Termin berechnen.");
// Warte 60 Sekunden und versuche es erneut
task = Bukkit.getScheduler().runTaskLater(plugin, this::scheduleNextReset, 20L * 60);
return;
}
long ticks = delay.getSeconds() * 20L;
plugin.getLogger().info("Nächster geplanter Reset in "
+ delay.toHours() + "h " + (delay.toMinutes() % 60) + "min (" + ticks + " Ticks).");
task = Bukkit.getScheduler().runTaskLater(plugin, () -> {
if (stopped) return; // BUG FIX #4 (Fortsetzung): Doppelte Prüfung im Callback
plugin.getLogger().info("Geplanter Reset wird jetzt ausgeführt...");
resetManager.resetWorld();
scheduleNextReset();
}, ticks);
}
public void stop() {
stopped = true;
if (task != null) {
task.cancel();
task = null;
}
}
public boolean isRunning() {
return !stopped && task != null;
}
}

View File

@@ -0,0 +1,40 @@
package de.viper.autoworldreset.util;
import com.cronutils.model.Cron;
import com.cronutils.model.time.ExecutionTime;
import com.cronutils.parser.CronParser;
import com.cronutils.model.definition.CronDefinitionBuilder;
import java.time.Duration;
import java.time.ZonedDateTime;
// BUG FIX #1: CronType von UNIX auf QUARTZ geändert.
// UNIX-Cron hat nur 5 Felder (min h dom mon dow), aber config.yml verwendet
// einen 6-feldrigen Quartz-Ausdruck ("0 30 18 * * *" = Sekunde/Minute/Stunde/...).
// Zur Laufzeit warf das einen IllegalArgumentException beim Parsen.
import static com.cronutils.model.CronType.QUARTZ;
public class CronParserUtil {
public static Duration parseCronToDelay(String cronExpression) {
// BUG FIX #2: Null-/Leerstring-Check verhindert NullPointerException,
// wenn der Schlüssel "scheduler.cron" in der config.yml fehlt.
if (cronExpression == null || cronExpression.isBlank()) {
return null;
}
try {
CronParser parser = new CronParser(CronDefinitionBuilder.instanceDefinitionFor(QUARTZ));
Cron cron = parser.parse(cronExpression);
cron.validate(); // BUG FIX #3: Explizite Validierung für frühzeitige,
// verständliche Fehlermeldungen bei ungültigen Ausdrücken.
ExecutionTime executionTime = ExecutionTime.forCron(cron);
ZonedDateTime now = ZonedDateTime.now();
return executionTime.timeToNextExecution(now).orElse(null);
} catch (Exception e) {
System.err.println("[AutoWorldReset] Ungültiger Cron-Ausdruck '"
+ cronExpression + "': " + e.getMessage());
return null;
}
}
}

View File

@@ -1,731 +0,0 @@
package me.zombie_striker.sr;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPSClient;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Chunk;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.bukkit.scheduler.BukkitTask;
import java.io.*;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.Deflater;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
public class Main extends JavaPlugin {
private static List<String> exceptions = new ArrayList<String>();
private static String prefix = "&6[&3ServerRestorer-Reborn&6]&8";
private static String kickmessage = " Server wird auf den vorherigen Speicherstand zurückgesetzt. Bitte trete in wenigen Sekunden erneut bei.";
BukkitTask br = null;
private boolean saveTheConfig = false;
private long lastSave = 0;
private long timedist = 0;
private File master = null;
private File backups = null;
private boolean saveServerJar = false;
private boolean savePluiginJars = false;
private boolean currentlySaving = false;
private boolean automate = true;
private boolean useFTP = false;
private boolean useFTPS = false;
private boolean useSFTP = false;
private String serverFTP = "www.example.com";
private String userFTP = "User";
private String passwordFTP = "password";
private int portFTP = 80;
private String naming_format = "Backup-%date%";
private SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
private String removeFilePath = "";
private long maxSaveSize = -1;
private int maxSaveFiles = 1000;
private boolean deleteZipOnFail = false;
private boolean deleteZipOnFTP = false;
private int hourToSaveAt = -1;
private String separator = File.separator;
private int compression = Deflater.BEST_COMPRESSION;
public static File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
File destFile = new File(destinationDir, zipEntry.getName());
String destDirPath = destinationDir.getCanonicalPath();
String destFilePath = destFile.getCanonicalPath();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
}
return destFile;
}
private static boolean isExempt(String path) {
path = path.toLowerCase().trim();
for (String s : exceptions)
if (path.endsWith(s.toLowerCase().trim()))
return true;
return false;
}
public static String humanReadableByteCount(long bytes, boolean si) {
int unit = si ? 1000 : 1024;
if (bytes < unit)
return bytes + " B";
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i");
return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
}
public static long folderSize(File directory) {
long length = 0;
if(directory==null)return -1;
for (File file : directory.listFiles()) {
if (file.isFile())
length += file.length();
else
length += folderSize(file);
}
return length;
}
public static File firstFileModified(File dir) {
File fl = dir;
File[] files = fl.listFiles(new FileFilter() {
public boolean accept(File file) {
return file.isFile();
}
});
long lastMod = Long.MAX_VALUE;
File choice = null;
for (File file : files) {
if (file.lastModified() < lastMod) {
choice = file;
lastMod = file.lastModified();
}
}
return choice;
}
public File getMasterFolder() {
return master;
}
public File getBackupFolder() {
return backups;
}
public long a(String path, long def) {
if (getConfig().contains(path))
return getConfig().getLong(path);
saveTheConfig = true;
getConfig().set(path, def);
return def;
}
public Object a(String path, Object def) {
if (getConfig().contains(path))
return getConfig().get(path);
saveTheConfig = true;
getConfig().set(path, def);
return def;
}
@SuppressWarnings("unchecked")
@Override
public void onEnable() {
master = getDataFolder().getAbsoluteFile().getParentFile().getParentFile();
String path = ((String) a("getBackupFileDirectory", ""));
backups = new File((path.isEmpty() ? master.getPath() : path) + File.separator+"backups"+ File.separator);
if (!backups.exists())
backups.mkdirs();
saveServerJar = (boolean) a("saveServerJar", false);
savePluiginJars = (boolean) a("savePluginJars", false);
timedist = toTime((String) a("AutosaveDelay", "1D,0H"));
lastSave = a("LastAutosave", 0L);
automate = (boolean) a("enableautoSaving", true);
naming_format = (String) a("FileNameFormat", naming_format);
String unPrefix = (String) a("prefix", "&6[&3ServerRestorer&6]&8");
prefix = ChatColor.translateAlternateColorCodes('&', unPrefix);
String kicky = (String) a("kickMessage", unPrefix + " Restoring server to previous save. Please rejoin in a few seconds.");
kickmessage = ChatColor.translateAlternateColorCodes('&', kicky);
useFTP = (boolean) a("EnableFTP", false);
useFTPS = (boolean) a("EnableFTPS", false);
useSFTP = (boolean) a("EnableSFTP", false);
serverFTP = (String) a("FTPAdress", serverFTP);
portFTP = (int) a("FTPPort", portFTP);
userFTP = (String) a("FTPUsername", userFTP);
passwordFTP = (String) a("FTPPassword", passwordFTP);
compression = (int) a("CompressionLevel_Max_9", compression);
removeFilePath = (String) a("FTP_Directory", removeFilePath);
hourToSaveAt = (int) a("AutoBackup-HourToBackup", hourToSaveAt);
if (!getConfig().contains("exceptions")) {
exceptions.add("logs");
exceptions.add("crash-reports");
exceptions.add("backups");
exceptions.add("dynmap");
exceptions.add(".lock");
exceptions.add("pixelprinter");
}
exceptions = (List<String>) a("exceptions", exceptions);
maxSaveSize = toByteSize((String) a("MaxSaveSize", "10G"));
maxSaveFiles = (int) a("MaxFileSaved", 1000);
deleteZipOnFTP = (boolean) a("DeleteZipOnFTPTransfer", false);
deleteZipOnFail = (boolean) a("DeleteZipIfFailed", false);
separator = (String) a("FolderSeparator", separator);
if (saveTheConfig)
saveConfig();
if (automate) {
final JavaPlugin thi = this;
br = new BukkitRunnable() {
@Override
public void run() {
Calendar calendar = GregorianCalendar.getInstance(); // creates a new calendar instance
calendar.setTime(new Date()); // assigns calendar to given date
int hour = calendar.get(Calendar.HOUR_OF_DAY);
if (System.currentTimeMillis() - lastSave >= timedist && (hourToSaveAt==-1 || hourToSaveAt == hour)) {
new BukkitRunnable() {
@Override
public void run() {
getConfig().set("LastAutosave", lastSave = (System.currentTimeMillis()-5000));
save(Bukkit.getConsoleSender());
saveConfig();
}
}.runTaskLater(thi, 0);
return;
}
}
}.runTaskTimerAsynchronously(this, 20, 20*60);
}
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
if (args.length == 1) {
List<String> list = new ArrayList<>();
String[] commands = new String[]{"disableAutoSaver", "enableAutoSaver", "restore", "save","stop", "toggleOptions"};
for (String f : commands) {
if (f.toLowerCase().startsWith(args[0].toLowerCase()))
list.add(f);
}
return list;
}
if (args.length > 1) {
if (args[0].equalsIgnoreCase("restore")) {
List<String> list = new ArrayList<>();
for (File f : getBackupFolder().listFiles()) {
if (f.getName().toLowerCase().startsWith(args[1].toLowerCase()))
list.add(f.getName());
}
return list;
}
}
return super.onTabComplete(sender, command, alias, args);
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!sender.hasPermission("serverrestorer.command")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (args.length == 0) {
sender.sendMessage(ChatColor.GOLD + "---===+Server Restorer+===---");
sender.sendMessage("/sr save : Saves the server");
sender.sendMessage("/sr stop : Stops creating a backup of the server");
sender.sendMessage("/sr restore <backup> : Restores server to previous backup (automatically restarts)");
sender.sendMessage("/sr enableAutoSaver [1H,6H,1D,7D] : Configure how long it takes to autosave");
sender.sendMessage("/sr disableAutoSaver : Disables the autosaver");
sender.sendMessage("/sr toggleOptions : TBD");
return true;
}
if (args[0].equalsIgnoreCase("restore")) {
if(true) {
sender.sendMessage(prefix+ "Restore feature is temporarily disabled. Please load the files manually.");
return true;
}
if (!sender.hasPermission("serverrestorer.restore")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (currentlySaving) {
sender.sendMessage(prefix + " The server is currently being saved. Please wait.");
return true;
}
if (args.length < 2) {
sender.sendMessage(prefix + " A valid backup file is required.");
return true;
}
File backup = new File(getBackupFolder(), args[1]);
if (!backup.exists()) {
sender.sendMessage(prefix + " The file \"" + args[1] + "\" does not exist.");
return true;
}
restore(backup);
sender.sendMessage(prefix + " Restoration complete.");
return true;
}
if (args[0].equalsIgnoreCase("stop")) {
if (!sender.hasPermission("serverrestorer.save")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (currentlySaving) {
currentlySaving=false;
return true;
}
sender.sendMessage(prefix + " The server is not currently being saved.");
return true;
}
if (args[0].equalsIgnoreCase("save")) {
if (!sender.hasPermission("serverrestorer.save")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (currentlySaving) {
sender.sendMessage(prefix + " The server is currently being saved. Please wait.");
return true;
}
save(sender);
return true;
}
if (args[0].equalsIgnoreCase("disableAutoSaver")) {
if (!sender.hasPermission("serverrestorer.save")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (br != null)
br.cancel();
br = null;
getConfig().set("enableautoSaving", false);
saveConfig();
sender.sendMessage(prefix + " Canceled delay.");
}
if (args[0].equalsIgnoreCase("enableAutoSaver")) {
if (!sender.hasPermission("serverrestorer.save")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
if (args.length == 1) {
sender.sendMessage(prefix + " Please select a delay [E.G. 0.5H, 6H, 1D, 7D...]");
return true;
}
String delay = args[1];
getConfig().set("AutosaveDelay", delay);
getConfig().set("enableautoSaving", true);
saveConfig();
if (br != null)
br.cancel();
br = null;
br = new BukkitRunnable() {
@Override
public void run() {
if (System.currentTimeMillis() - lastSave > timedist) {
save(Bukkit.getConsoleSender());
getConfig().set("LastAutosave", lastSave = System.currentTimeMillis());
saveConfig();
return;
}
}
}.runTaskTimerAsynchronously(this, 20, 20 * 60 * 30);
sender.sendMessage(prefix + " Set the delay to \"" + delay + "\".");
}
if (args[0].equalsIgnoreCase("toggleOptions")) {
if (!sender.hasPermission("serverrestorer.save")) {
sender.sendMessage(prefix + ChatColor.RED + " You do not have permission to use this command.");
return true;
}
sender.sendMessage(prefix + " Coming soon !");
return true;
}
return true;
}
public void save(CommandSender sender) {
currentlySaving = true;
sender.sendMessage(prefix + " Starting to save directory. Please wait.");
List<World> autosave = new ArrayList<>();
for (World loaded : Bukkit.getWorlds()) {
try {
loaded.save();
if (loaded.isAutoSave()) {
autosave.add(loaded);
loaded.setAutoSave(false);
}
} catch (Exception e) {
}
}
new BukkitRunnable() {
@Override
public void run() {
try {
try {
if(backups.listFiles().length > maxSaveFiles){
for(int i = 0; i < backups.listFiles().length-maxSaveFiles; i++){
File oldestBack = firstFileModified(backups);
sender.sendMessage(prefix + ChatColor.RED + oldestBack.getName()
+ ": File goes over max amount of files that can be saved.");
oldestBack.delete();
}
}
for (int j = 0; j < Math.min(maxSaveFiles, backups.listFiles().length - 1); j++) {
if (folderSize(backups) >= maxSaveSize) {
File oldestBack = firstFileModified(backups);
sender.sendMessage(prefix + ChatColor.RED + oldestBack.getName()
+ ": The current save goes over the max savesize, and so the oldest file has been deleted. If you wish to save older backups, copy them to another location.");
oldestBack.delete();
} else {
break;
}
}
} catch (Error | Exception e) {
}
final long time = lastSave = System.currentTimeMillis();
Date d = new Date(lastSave);
File zipFile = new File(getBackupFolder(),
naming_format.replaceAll("%date%", dateformat.format(d)) + ".zip");
if (!zipFile.exists()) {
zipFile.getParentFile().mkdirs();
zipFile = new File(getBackupFolder(),
naming_format.replaceAll("%date%", dateformat.format(d)) + ".zip");
zipFile.createNewFile();
}
zipFolder(getMasterFolder().getPath(), zipFile.getPath());
long timeDif = (System.currentTimeMillis() - time) / 1000;
String timeDifS = (((int) (timeDif / 60)) + "M, " + (timeDif % 60) + "S");
if(!currentlySaving){
for (World world : autosave)
world.setAutoSave(true);
sender.sendMessage(prefix + " Backup canceled.");
cancel();
return;
}
sender.sendMessage(prefix + " Done! Backup took:" + timeDifS);
File tempBackupCheck = new File(getMasterFolder(), "backups");
sender.sendMessage(prefix + " Compressed server with size of "
+ (humanReadableByteCount(folderSize(getMasterFolder())
- (tempBackupCheck.exists() ? folderSize(tempBackupCheck) : 0), false))
+ " to " + humanReadableByteCount(zipFile.length(), false));
currentlySaving = false;
for (World world : autosave)
world.setAutoSave(true);
if (useSFTP) {
try {
sender.sendMessage(prefix + " Starting SFTP Transfer");
JSch jsch = new JSch();
Session session = jsch.getSession(userFTP, serverFTP, portFTP);
session.setConfig("PreferredAuthentications", "password");
session.setPassword(passwordFTP);
session.connect(1000 * 20);
Channel channel = session.openChannel("sftp");
ChannelSftp sftp = (ChannelSftp) channel;
sftp.connect(1000 * 20);
} catch (Exception | Error e) {
sender.sendMessage(
prefix + " FAILED TO SFTP TRANSFER FILE: " + zipFile.getName() + ". ERROR IN CONSOLE.");
if (deleteZipOnFail)
zipFile.delete();
e.printStackTrace();
}
} else if (useFTPS) {
sender.sendMessage(prefix + " Starting FTPS Transfer");
FileInputStream zipFileStream = new FileInputStream(zipFile);
FTPSClient ftpClient = new FTPSClient();
try {
if (ftpClient.isConnected()) {
sender.sendMessage(prefix + "FTPSClient was already connected. Disconnecting");
ftpClient.logout();
ftpClient.disconnect();
ftpClient = new FTPSClient();
}
sendFTP(sender, zipFile, ftpClient, zipFileStream, removeFilePath);
if (deleteZipOnFTP)
zipFile.delete();
} catch (Exception | Error e) {
sender.sendMessage(
prefix + " FAILED TO FTPS TRANSFER FILE: " + zipFile.getName() + ". ERROR IN CONSOLE.");
if (deleteZipOnFail)
zipFile.delete();
e.printStackTrace();
} finally {
try {
if (ftpClient.isConnected()) {
sender.sendMessage(prefix + "Disconnecting");
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
} else if (useFTP) {
sender.sendMessage(prefix + " Starting FTP Transfer");
FileInputStream zipFileStream = new FileInputStream(zipFile);
FTPClient ftpClient = new FTPClient();
try {
if (ftpClient.isConnected()) {
sender.sendMessage(prefix + "FTPClient was already connected. Disconnecting");
ftpClient.logout();
ftpClient.disconnect();
ftpClient = new FTPClient();
}
sendFTP(sender, zipFile, ftpClient, zipFileStream, removeFilePath);
if (deleteZipOnFTP)
zipFile.delete();
} catch (Exception | Error e) {
sender.sendMessage(
prefix + " FAILED TO FTP TRANSFER FILE: " + zipFile.getName() + ". ERROR IN CONSOLE.");
if (deleteZipOnFail)
zipFile.delete();
e.printStackTrace();
} finally {
try {
if (ftpClient.isConnected()) {
sender.sendMessage(prefix + "Disconnecting");
ftpClient.logout();
ftpClient.disconnect();
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.runTaskAsynchronously(this);
}
public void sendFTP(CommandSender sender, File zipFile, FTPClient ftpClient, FileInputStream zipFileStream, String path)
throws SocketException, IOException {
ftpClient.connect(serverFTP, portFTP);
ftpClient.login(userFTP, passwordFTP);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
boolean done = ftpClient.storeFile(path + zipFile.getName(), zipFileStream);
zipFileStream.close();
if (done) {
sender.sendMessage(prefix + " Transfered backup using FTP!");
} else {
sender.sendMessage(prefix + " Something failed (maybe)! Status=" + ftpClient.getStatus());
}
}
public long toTime(String time) {
long militime = 0;
for(String split : time.split(",")) {
split = split.trim();
long k = 1;
if (split.toUpperCase().endsWith("H")) {
k *= 60 * 60;
} else if (split.toUpperCase().endsWith("D")) {
k *= 60 * 60 * 24;
} else {
k *= 60 * 60 * 24;
}
double j = Double.parseDouble(split.substring(0, split.length() - 1));
militime += (j*k);
}
militime *= 1000;
return militime;
}
public void restore(File backup) {
//Kick all players
for (Player player : Bukkit.getOnlinePlayers())
player.kickPlayer(kickmessage);
//Disable all plugins safely.
for (Plugin p : Bukkit.getPluginManager().getPlugins()) {
if (p != this) {
try {
Bukkit.getPluginManager().disablePlugin(p);
} catch (Exception e) {
e.printStackTrace();
}
}
}
//Unload all worlds.
List<String> names = new ArrayList<>();
for (World w : Bukkit.getWorlds()) {
for (Chunk c : w.getLoadedChunks()) {
c.unload(false);
}
names.add(w.getName());
Bukkit.unloadWorld(w, true);
}
for(String worldnames : names){
File worldFile = new File(getMasterFolder(),worldnames);
if(worldFile.exists())
worldFile.delete();
}
//Start overriding files.
File parentTo = getMasterFolder().getParentFile();
try {
byte[] buffer = new byte[1024];
ZipInputStream zis = new ZipInputStream(new FileInputStream(backup));
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
try {
File newFile = newFile(parentTo, zipEntry);
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
zipEntry = zis.getNextEntry();
} catch (Exception e) {
e.printStackTrace();
}
}
zis.closeEntry();
zis.close();
} catch (Exception e4) {
e4.printStackTrace();
}
Bukkit.shutdown();
}
public void zipFolder(String srcFolder, String destZipFile) throws Exception {
ZipOutputStream zip = null;
FileOutputStream fileWriter = null;
fileWriter = new FileOutputStream(destZipFile);
zip = new ZipOutputStream(fileWriter);
zip.setLevel(compression);
addFolderToZip("", srcFolder, zip);
zip.flush();
zip.close();
}
private void addFileToZip(String path, String srcFile, ZipOutputStream zip) {
try {
File folder = new File(srcFile);
if (!isExempt(srcFile)) {
if(!currentlySaving)
return;
// this.savedBytes += folder.length();
if (folder.isDirectory()) {
addFolderToZip(path, srcFile, zip);
} else {
if (folder.getName().endsWith("jar")) {
if (path.contains("plugins") && (!savePluiginJars) || (!path.contains("plugins") && (!saveServerJar))) {
return;
}
}
byte[] buf = new byte['?'];
FileInputStream in = new FileInputStream(srcFile);
zip.putNextEntry(new ZipEntry(path + separator + folder.getName()));
int len;
while ((len = in.read(buf)) > 0) {
zip.write(buf, 0, len);
}
in.close();
}
}
}catch (FileNotFoundException e4){
Bukkit.getConsoleSender().sendMessage(prefix + " FAILED TO ZIP FILE: " + srcFile+" Reason: "+e4.getClass().getName());
e4.printStackTrace();
}catch (IOException e5){
if(!srcFile.endsWith(".db")) {
Bukkit.getConsoleSender().sendMessage(prefix + " FAILED TO ZIP FILE: " + srcFile + " Reason: " + e5.getClass().getName());
e5.printStackTrace();
}else{
Bukkit.getConsoleSender().sendMessage(prefix + " Skipping file " + srcFile +" due to another process that has locked a portion of the file");
}
}
}
private void addFolderToZip(String path, String srcFolder, ZipOutputStream zip) {
if ((!path.toLowerCase().contains("backups")) && (!isExempt(path))) {
try {
File folder = new File(srcFolder);
String[] arrayOfString;
int j = (arrayOfString = folder.list()).length;
for (int i = 0; i < j; i++) {
if(!currentlySaving)
break;
String fileName = arrayOfString[i];
if (path.equals("")) {
addFileToZip(folder.getName(), srcFolder + separator + fileName, zip);
} else {
addFileToZip(path + separator + folder.getName(), srcFolder + separator + fileName, zip);
}
}
} catch (Exception e) {
}
}
}
private long toByteSize(String s) {
long k = Long.parseLong(s.substring(0, s.length() - 1));
if (s.toUpperCase().endsWith("G")) {
k *= 1000 * 1000 * 1000;
} else if (s.toUpperCase().endsWith("M")) {
k *= 1000 * 1000;
} else if (s.toUpperCase().endsWith("K")) {
k *= 1000;
} else {
k *= 10;
}
return k;
}
}

View File

@@ -1,756 +0,0 @@
package me.zombie_striker.sr;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitRunnable;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.logging.Level;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/**
* Checks and auto updates a plugin<br>
* <br>
* <b>You must have a option in your config to disable the updater!</b>
*
* @author Arsen
*/
public class Updater {
private static final String HOST = "https://api.curseforge.com";
private static final String QUERY = "/servermods/files?projectIds=";
private static final String AGENT = "Mozilla/5.0 Updater by ArsenArsen";
private static final File WORKING_DIR = new File("plugins" + File.separator + "AUpdater" + File.separator);
private static final File BACKUP_DIR = new File(WORKING_DIR, "backups" + File.separator);
private static final File LOG_FILE = new File(WORKING_DIR, "updater.log");
private static final File CONFIG_FILE = new File(WORKING_DIR, "global.yml");
private static final char[] HEX_CHAR_ARRAY = "0123456789abcdef".toCharArray();
private static final Pattern NAME_MATCH = Pattern.compile(".+\\sv?[0-9.]+");
private static final String VERSION_SPLIT = "\\sv?";
private int id = -1;
private Plugin p;
private boolean debug = false;
private UpdateAvailability lastCheck = null;
private UpdateResult lastUpdate = UpdateResult.NOT_UPDATED;
private File pluginFile = null;
private String downloadURL = null;
private String futuremd5;
private String downloadName;
private List<Channel> allowedChannels = Arrays.asList(Channel.ALPHA, Channel.BETA, Channel.RELEASE);
private List<UpdateCallback> callbacks = new ArrayList<>();
private SyncCallbackCaller caller = new SyncCallbackCaller();
private List<String> skipTags = new ArrayList<>();
private String latest;
private FileConfiguration global;
public boolean updaterActive = false;
/**
* Makes the updater for a plugin
*
* @param p Plugin to update
*/
public Updater(Plugin p) {
this.p = p;
try {
pluginFile = new File(URLDecoder.decode(p.getClass().getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
debug(e.toString());
// Should not ever happen
}
latest = p.getDescription().getVersion();
if (!CONFIG_FILE.exists()) {
try {
CONFIG_FILE.getParentFile().mkdirs();
CONFIG_FILE.createNewFile();
log("Created config file!");
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Could not create " + CONFIG_FILE.getName() + "!", e);
}
}
global = YamlConfiguration.loadConfiguration(CONFIG_FILE);
global.options().header("Updater by ArsenArsen\nGlobal config\nSets should updates be downloaded globaly");
if (!global.isSet("update")) {
global.set("update", true);
try {
global.save(CONFIG_FILE);
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Could not save default config file!", e);
}
}
if (!LOG_FILE.exists()) {
try {
LOG_FILE.getParentFile().mkdirs();
LOG_FILE.createNewFile();
log("Created log file!");
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Could not create " + LOG_FILE.getName() + "!", e);
}
}
updaterActive = global.getBoolean("update");
}
/**
* Makes the updater for a plugin with an ID
*
* @param p The plugin
* @param id Plugin ID
*/
public Updater(Plugin p, int id) {
this(p);
setID(id);
}
/**
* Makes the updater for a plugin with an ID
*
* @param p The plugin
* @param id Plugin ID
* @param download Set to true if your plugin needs to be immediately downloaded
* @param skipTags Tags, endings of a filename, that updater will ignore, must begin with a dash ('-')
*/
public Updater(Plugin p, int id, boolean download, String... skipTags) {
this(p);
setID(id);
for (String tag : skipTags)
if (tag.startsWith("-"))
this.skipTags.add(tag);
if (download && (checkForUpdates() == UpdateAvailability.UPDATE_AVAILABLE)) {
update();
}
}
/**
* Makes the updater for a plugin with an ID
*
* @param p The plugin
* @param id Plugin ID
* @param download Set to true if your plugin needs to be immediately downloaded
* @param skipTags Tags, endings of a filename, that updater will ignore, or null for none
* @param callbacks All update callbacks you need
*/
public Updater(Plugin p, int id, boolean download, String[] skipTags, UpdateCallback... callbacks) {
this(p);
setID(id);
this.callbacks.addAll(Arrays.asList(callbacks));
if (skipTags != null) {
for (String tag : skipTags)
if (tag.startsWith("-"))
this.skipTags.add(tag);
}
if (global.getBoolean("update", true) && download && (checkForUpdates() == UpdateAvailability.UPDATE_AVAILABLE)) {
update();
}
}
/**
* Gets the plugin ID
*
* @return the plugin ID
*/
public int getID() {
return id;
}
/**
* Sets the plugin ID
*
* @param id The plugin ID
*/
public void setID(int id) {
this.id = id;
}
/**
* Adds a new callback
*
* @param callback Callback to register
*/
public void registerCallback(UpdateCallback callback) {
callbacks.add(callback);
}
/**
* Attempts a update
*
* @throws IllegalStateException if the ID was not set
*/
public void update() {
debug(WORKING_DIR.getAbsolutePath());
debug("Update!");
if (id == -1) {
throw new IllegalStateException("Plugin ID is not set!");
}
if (lastCheck == null) {
checkForUpdates();
}
if (!BACKUP_DIR.exists() || !BACKUP_DIR.isDirectory()) {
BACKUP_DIR.mkdir();
}
final Updater updater = this;
if (!global.getBoolean("update", true)) {
lastUpdate = UpdateResult.DISABLED;
debug("Disabled!");
caller.call(callbacks, UpdateResult.DISABLED, updater);
return;
}
if (lastCheck == UpdateAvailability.UPDATE_AVAILABLE) {
new BukkitRunnable() {
@Override
public void run() {
debug("Update STARTED!");
p.getLogger().info("Starting update of " + p.getName());
log("Updating " + p.getName() + "!");
lastUpdate = download(true);
p.getLogger().log(Level.INFO, "Update done! Result: " + lastUpdate);
caller.call(callbacks, lastUpdate, updater);
}
}.runTaskAsynchronously(p);
} else if (lastCheck == UpdateAvailability.SM_UNREACHABLE) {
lastUpdate = UpdateResult.IOERROR;
debug("Fail!");
caller.call(callbacks, UpdateResult.IOERROR, updater);
} else {
lastUpdate = UpdateResult.GENERAL_ERROR;
debug("Fail!");
caller.call(callbacks, UpdateResult.IOERROR, updater);
}
}
public UpdateResult download(boolean keepBackups) {
try {
if(keepBackups){
Files.copy(pluginFile.toPath(),
new File(BACKUP_DIR, "backup-" + System.currentTimeMillis() + "-" + p.getName() + ".jar").toPath(),
StandardCopyOption.REPLACE_EXISTING);
//TODO: Considering the amount of times the plugin updates, I don't want there to be a huge file full of old jars.
}
File downloadTo = new File(pluginFile.getParentFile().getAbsolutePath() +
File.separator + "AUpdater" + File.separator, downloadName);
downloadTo.getParentFile().mkdirs();
downloadTo.delete();
if(keepBackups)
debug("Started download!");
downloadIsSeperateBecauseGotoGotRemoved(downloadTo);
if(keepBackups){
debug("Ended download!");
if (!fileHash(downloadTo).equalsIgnoreCase(futuremd5))
return UpdateResult.BAD_HASH;
if (downloadTo.getName().endsWith(".jar")) {
pluginFile.setWritable(true, false);
pluginFile.delete();
if(keepBackups){
debug("Started copy!");
InputStream in = new FileInputStream(downloadTo);
File file = new File(pluginFile.getParentFile()
.getAbsoluteFile() + File.separator + "update" + File.separator, pluginFile.getName());
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream out = new FileOutputStream(file);
long bytes = copy(in, out);
p.getLogger().info("Update done! Downloaded " + bytes + " bytes!");
log("Updated plugin " + p.getName() + " with " + bytes + "bytes!");
}
return UpdateResult.UPDATE_SUCCEEDED;
} else
return unzip(downloadTo);
}else{
return UpdateResult.UPDATE_SUCCEEDED;
}
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Couldn't download update for " + p.getName(), e);
log("Failed to update " + p.getName() + "!", e);
return UpdateResult.IOERROR;
}
}
/**
* God damn it Gosling, <a href="http://stackoverflow.com/a/4547764/3809164">reference here.</a>
*/
private void downloadIsSeperateBecauseGotoGotRemoved(File downloadTo) throws IOException {
URL url = new URL(downloadURL);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("User-Agent", AGENT);
connection.connect();
if (connection.getResponseCode() >= 300 && connection.getResponseCode() < 400) {
downloadURL = connection.getHeaderField("Location");
downloadIsSeperateBecauseGotoGotRemoved(downloadTo);
} else {
debug(connection.getResponseCode() + " " + connection.getResponseMessage() + " when requesting " + downloadURL);
copy(connection.getInputStream(), new FileOutputStream(downloadTo));
}
}
private long copy(InputStream in, OutputStream out) throws IOException {
long bytes = 0;
byte[] buf = new byte[0x1000];
while (true) {
int r = in.read(buf);
if (r == -1)
break;
out.write(buf, 0, r);
bytes += r;
debug("Another 4K, current: " + r);
}
out.flush();
out.close();
in.close();
return bytes;
}
private UpdateResult unzip(File download) {
ZipFile zipFile = null;
try {
zipFile = new ZipFile(download);
Enumeration<? extends ZipEntry> entries = zipFile.entries();
ZipEntry entry;
File updateFile = new File(pluginFile.getParentFile()
.getAbsoluteFile() + File.separator + "update" + File.separator, pluginFile.getName());
while ((entry = entries.nextElement()) != null) {
File target = new File(updateFile, entry.getName());
File inPlugins = new File(pluginFile.getParentFile(), entry.getName());
if(!inPlugins.exists()){
target = inPlugins;
}
if (!entry.isDirectory()) {
target.getParentFile().mkdirs();
InputStream zipStream = zipFile.getInputStream(entry);
OutputStream fileStream = new FileOutputStream(target);
copy(zipStream, fileStream);
}
}
return UpdateResult.UPDATE_SUCCEEDED;
} catch (IOException e) {
if (e instanceof ZipException) {
p.getLogger().log(Level.SEVERE, "Could not unzip downloaded file!", e);
log("Update for " + p.getName() + "was an unknown filetype! ", e);
return UpdateResult.UNKNOWN_FILE_TYPE;
} else {
p.getLogger().log(Level.SEVERE,
"An IOException occured while trying to update %s!".replace("%s", p.getName()), e);
log("Update for " + p.getName() + "was an unknown filetype! ", e);
return UpdateResult.IOERROR;
}
} finally {
if (zipFile != null) {
try {
zipFile.close();
} catch (IOException ignored) {
}
}
}
}
/**
* Checks for new updates
*
* @param force Discards the cached state in order to get a new one, ignored if update check didn't run
* @return Is there any updates
* @throws IllegalStateException If the plugin ID is not set
*/
public UpdateAvailability checkForUpdates(boolean force) {
if (id == -1) {
throw new IllegalStateException("Plugin ID is not set!");
}
if (force || lastCheck == null) {
String target = HOST + QUERY + id;
debug(target);
try {
URL url = new URL(target);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.addRequestProperty("User-Agent", AGENT);
connection.connect();
debug("Connecting!");
BufferedReader responseReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder responseBuffer = new StringBuilder();
String line;
while ((line = responseReader.readLine()) != null) {
responseBuffer.append(line);
}
debug("All read!");
responseReader.close();
String response = responseBuffer.toString();
int counter = 1;
if (connection.getResponseCode() == 200) {
try {
debug("RESCODE 200");
while (true) {
debug("Counter: " + counter);
JSONParser parser = new JSONParser();
JSONArray json = (JSONArray) parser.parse(response);
if (json.size() - counter < 0) {
lastCheck = UpdateAvailability.NO_UPDATE;
debug("No update!");
break;
}
JSONObject latest = (JSONObject) json.get(json.size() - counter);
futuremd5 = (String) latest.get("md5");
String channel = (String) latest.get("releaseType");
String name = (String) latest.get("name");
if (allowedChannels.contains(Channel.matchChannel(channel.toUpperCase()))
&& !hasTag(name)) {
String noTagName = name;
String oldVersion = p.getDescription().getVersion().replaceAll("-.*", "");
for (String tag : skipTags) {
noTagName = noTagName.replace(tag, "");
oldVersion = oldVersion.replace(tag, "");
}
if (!NAME_MATCH.matcher(noTagName).matches()) {
lastCheck = UpdateAvailability.CANT_PARSE_NAME;
return lastCheck;
}
String[] splitName = noTagName.split(VERSION_SPLIT);
String version = splitName[splitName.length - 1];
if (oldVersion.length() > version.length()) {
while (oldVersion.length() > version.length()) {
version += ".0";
}
} else if (oldVersion.length() < version.length()) {
while (oldVersion.length() < version.length()) {
oldVersion += ".0";
}
}
debug("Versions are same length");
String[] splitOldVersion = oldVersion.split("\\.");
String[] splitVersion = version.split("\\.");
Integer[] parsedOldVersion = new Integer[splitOldVersion.length];
Integer[] parsedVersion = new Integer[splitVersion.length];
for (int i = 0; i < parsedOldVersion.length; i++) {
parsedOldVersion[i] = Integer.parseInt(splitOldVersion[i]);
}
for (int i = 0; i < parsedVersion.length; i++) {
parsedVersion[i] = Integer.parseInt(splitVersion[i]);
}
boolean update = false;
for (int i = 0; i < parsedOldVersion.length; i++) {
if (parsedOldVersion[i] < parsedVersion[i]) {
update = true;
break;
}
}
if (!update) {
lastCheck = UpdateAvailability.NO_UPDATE;
//Temp fix for downloads
downloadURL = ((String) latest.get("downloadUrl")).replace(" ", "%20");
downloadName = (String) latest.get("fileName");
} else {
lastCheck = UpdateAvailability.UPDATE_AVAILABLE;
downloadURL = ((String) latest.get("downloadUrl")).replace(" ", "%20");
downloadName = (String) latest.get("fileName");
}
break;
} else
counter++;
}
debug("While loop over!");
} catch (ParseException e) {
p.getLogger().log(Level.SEVERE, "Could not parse API Response for " + target, e);
log("Could not parse API Response for " + target + " while updating " + p.getName(), e);
lastCheck = UpdateAvailability.CANT_UNDERSTAND;
}
} else {
log("Could not reach API for " + target + " while updating " + p.getName());
lastCheck = UpdateAvailability.SM_UNREACHABLE;
}
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Could not check for updates for plugin " + p.getName(), e);
log("Could not reach API for " + target + " while updating " + p.getName(), e);
lastCheck = UpdateAvailability.SM_UNREACHABLE;
}
}
log("Update check ran for " + p.getName() + "! Check resulted in " + lastCheck);
return lastCheck;
}
private void debug(String message) {
if (debug)
p.getLogger().info(message + ' ' + new Throwable().getStackTrace()[1]);
}
private void log(String message) {
try {
Files.write(LOG_FILE.toPath(), Collections.singletonList(
"[" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "] " + message),
StandardCharsets.UTF_8, StandardOpenOption.APPEND);
} catch (IOException e) {
p.getLogger().log(Level.SEVERE, "Could not log to " + LOG_FILE.getAbsolutePath() + "!", e);
}
}
private void log(String message, Exception exception) {
StringWriter string = new StringWriter();
PrintWriter print = new PrintWriter(string);
exception.printStackTrace(print);
log(message + " " + string.toString());
try {
string.close();
} catch (IOException ignored) {
}
print.close();
}
private boolean hasTag(String name) {
for (String tag : skipTags) {
if (name.toLowerCase().endsWith(tag.toLowerCase())) {
return true;
}
}
return false;
}
/**
* Checks for new updates, non forcing cache override
*
* @return Is there any updates
* @throws IllegalStateException If the plugin ID is not set
*/
public UpdateAvailability checkForUpdates() {
return checkForUpdates(false);
}
/**
* Checks did the update run successfully
*
* @return The update state
*/
public UpdateResult isUpdated() {
return lastUpdate;
}
/**
* Sets allowed channels, AKA release types
*
* @param channels The allowed channels
*/
public void setChannels(Channel... channels) {
allowedChannels.clear();
allowedChannels.addAll(Arrays.asList(channels));
}
/**
* Gets the latest version
*
* @return The latest version
*/
public String getLatest() {
return latest;
}
/**
* Shows the outcome of an update
*
* @author Arsen
*/
public enum UpdateResult {
/**
* Update was successful
*/
UPDATE_SUCCEEDED,
/**
* Update was not attempted yet
*/
NOT_UPDATED,
/**
* Could not unpack the update
*/
UNKNOWN_FILE_TYPE,
/**
* Miscellanies error occurred while update checking
*/
GENERAL_ERROR,
/**
* Updater is globally disabled
*/
DISABLED,
/**
* The hashing algorithm and the remote hash had different results.
*/
BAD_HASH,
/**
* An unknown IO error occurred
*/
IOERROR
}
/**
* Shows the outcome of an update check
*
* @author Arsen
*/
public enum UpdateAvailability {
/**
* There is an update
*/
UPDATE_AVAILABLE,
/**
* You have the latest version
*/
NO_UPDATE,
/**
* Could not reach server mods API
*/
SM_UNREACHABLE,
/**
* Update name cannot be parsed, meaning the version cannot be compared
*/
CANT_PARSE_NAME,
/**
* Could not parse response from server mods API
*/
CANT_UNDERSTAND
}
public enum Channel {
/**
* Normal release
*/
RELEASE("release"),
/**
* Beta release
*/
BETA("beta"),
/**
* Alpha release
*/
ALPHA("alpha");
private String channel;
Channel(String channel) {
this.channel = channel;
}
/**
* Gets the channel value
*
* @return the channel value
*/
public String getChannel() {
return channel;
}
/**
* Returns channel whose channel value matches the given string
*
* @param channel The channel value
* @return The Channel constant
*/
public static Channel matchChannel(String channel) {
for (Channel c : values()) {
if (c.channel.equalsIgnoreCase(channel)) {
return c;
}
}
return null;
}
}
/**
* Calculates files MD5 hash
*
* @param file The file to digest
* @return The MD5 hex or null, if the operation failed
*/
public String fileHash(File file) {
FileInputStream is;
try {
is = new FileInputStream(file);
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] bytes = new byte[2048];
int numBytes;
while ((numBytes = is.read(bytes)) != -1) {
md.update(bytes, 0, numBytes);
}
byte[] digest = md.digest();
char[] hexChars = new char[digest.length * 2];
for (int j = 0; j < digest.length; j++) {
int v = digest[j] & 0xFF;
hexChars[j * 2] = HEX_CHAR_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_CHAR_ARRAY[v & 0x0F];
}
is.close();
return new String(hexChars);
} catch (IOException | NoSuchAlgorithmException e) {
p.getLogger().log(Level.SEVERE, "Could not digest " + file.getPath(), e);
return null;
}
}
/**
* Called right after update is done
*
* @author Arsen
*/
public interface UpdateCallback {
void updated(UpdateResult updateResult, Updater updater);
}
private class SyncCallbackCaller extends BukkitRunnable {
private List<UpdateCallback> callbacks;
private UpdateResult updateResult;
private Updater updater;
public void run() {
for (UpdateCallback callback : callbacks) {
callback.updated(updateResult, updater);
}
}
void call(List<UpdateCallback> callbacks, UpdateResult updateResult, Updater updater) {
this.callbacks = callbacks;
this.updateResult = updateResult;
this.updater = updater;
if (!Bukkit.getServer().isPrimaryThread())
runTask(updater.p);
else run();
}
}
}

Some files were not shown because too many files have changed in this diff Show More