Compare commits

..

2 Commits

Author SHA1 Message Date
dordsor21
5f53ace8ea cleanup 2025-08-24 15:09:04 +01:00
dordsor21
38904712e5 fix: improve error handling in backups
- fixes #4460
2025-08-24 14:51:15 +01:00
9 changed files with 173 additions and 76 deletions

View File

@@ -9,7 +9,7 @@ jobs:
os: [ ubuntu-latest, windows-latest, macos-latest ] os: [ ubuntu-latest, windows-latest, macos-latest ]
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4 uses: gradle/actions/wrapper-validation@v4
- name: Setup Java - name: Setup Java

View File

@@ -9,7 +9,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Validate Gradle Wrapper - name: Validate Gradle Wrapper
uses: gradle/actions/wrapper-validation@v4 uses: gradle/actions/wrapper-validation@v4
- name: Setup Java - name: Setup Java

View File

@@ -20,7 +20,7 @@ jobs:
language: [ 'java' ] language: [ 'java' ]
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v4
- name: Setup Java - name: Setup Java
uses: actions/setup-java@v4 uses: actions/setup-java@v4
with: with:

View File

@@ -18,6 +18,8 @@
*/ */
package com.plotsquared.core.backup; package com.plotsquared.core.backup;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException; import java.io.IOException;
@@ -30,12 +32,14 @@ import java.nio.file.Path;
*/ */
public class Backup { public class Backup {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Backup.class.getSimpleName());
private final BackupProfile owner; private final BackupProfile owner;
private final long creationTime; private final long creationTime;
@Nullable @Nullable
private final Path file; private final Path file;
Backup(final BackupProfile owner, final long creationTime, final Path file) { Backup(final BackupProfile owner, final long creationTime, @Nullable final Path file) {
this.owner = owner; this.owner = owner;
this.creationTime = creationTime; this.creationTime = creationTime;
this.file = file; this.file = file;
@@ -49,7 +53,7 @@ public class Backup {
try { try {
Files.deleteIfExists(file); Files.deleteIfExists(file);
} catch (final IOException e) { } catch (final IOException e) {
e.printStackTrace(); LOGGER.error("Error deleting back at {}", file, e);
} }
} }
} }

View File

@@ -21,14 +21,15 @@ package com.plotsquared.core.backup;
import com.google.inject.Inject; import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted; import com.google.inject.assistedinject.Assisted;
import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.ConsolePlayer; import com.plotsquared.core.exception.PlotSquaredException;
import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.schematic.Schematic; import com.plotsquared.core.plot.schematic.Schematic;
import com.plotsquared.core.util.SchematicHandler; import com.plotsquared.core.util.SchematicHandler;
import com.plotsquared.core.util.task.RunnableVal; import com.plotsquared.core.util.task.RunnableVal;
import com.plotsquared.core.util.task.TaskManager; import com.plotsquared.core.util.task.TaskManager;
import net.kyori.adventure.text.minimessage.MiniMessage; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -51,7 +52,7 @@ import java.util.concurrent.CompletableFuture;
*/ */
public class PlayerBackupProfile implements BackupProfile { public class PlayerBackupProfile implements BackupProfile {
static final MiniMessage MINI_MESSAGE = MiniMessage.builder().build(); private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PlayerBackupProfile.class.getSimpleName());
private final UUID owner; private final UUID owner;
private final Plot plot; private final Plot plot;
@@ -87,7 +88,7 @@ public class PlayerBackupProfile implements BackupProfile {
Files.createDirectory(path); Files.createDirectory(path);
} }
} catch (final Exception e) { } catch (final Exception e) {
e.printStackTrace(); LOGGER.error("Error resolving {} from {}", child, parent, e);
} }
return path; return path;
} }
@@ -104,7 +105,7 @@ public class PlayerBackupProfile implements BackupProfile {
try { try {
Files.createDirectories(path); Files.createDirectories(path);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error creating directory {}", path, e);
return Collections.emptyList(); return Collections.emptyList();
} }
} }
@@ -117,11 +118,11 @@ public class PlayerBackupProfile implements BackupProfile {
backups.add( backups.add(
new Backup(this, basicFileAttributes.creationTime().toMillis(), file)); new Backup(this, basicFileAttributes.creationTime().toMillis(), file));
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error getting attributes for file {} to create backup", file, e);
} }
}); });
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error walking files from {}", path, e);
} }
backups.sort(Comparator.comparingLong(Backup::getCreationTime).reversed()); backups.sort(Comparator.comparingLong(Backup::getCreationTime).reversed());
return (this.backupCache = backups); return (this.backupCache = backups);
@@ -133,7 +134,7 @@ public class PlayerBackupProfile implements BackupProfile {
public void destroy() { public void destroy() {
this.listBackups().whenCompleteAsync((backups, error) -> { this.listBackups().whenCompleteAsync((backups, error) -> {
if (error != null) { if (error != null) {
error.printStackTrace(); LOGGER.error("Error while listing backups", error);
} }
backups.forEach(Backup::delete); backups.forEach(Backup::delete);
this.backupCache = null; this.backupCache = null;
@@ -141,10 +142,12 @@ public class PlayerBackupProfile implements BackupProfile {
} }
public @NonNull Path getBackupDirectory() { public @NonNull Path getBackupDirectory() {
return resolve(resolve( return resolve(
resolve(
resolve(backupManager.getBackupPath(), Objects.requireNonNull(plot.getArea().toString(), "plot area id")), resolve(backupManager.getBackupPath(), Objects.requireNonNull(plot.getArea().toString(), "plot area id")),
Objects.requireNonNull(plot.getId().toDashSeparatedString(), "plot id") Objects.requireNonNull(plot.getId().toDashSeparatedString(), "plot id")
), Objects.requireNonNull(owner.toString(), "owner")); ), Objects.requireNonNull(owner.toString(), "owner")
);
} }
@Override @Override
@@ -156,7 +159,8 @@ public class PlayerBackupProfile implements BackupProfile {
backups.get(backups.size() - 1).delete(); backups.get(backups.size() - 1).delete();
} }
final List<Plot> plots = Collections.singletonList(plot); final List<Plot> plots = Collections.singletonList(plot);
final boolean result = this.schematicHandler.exportAll(plots, getBackupDirectory().toFile(), final boolean result = this.schematicHandler.exportAll(
plots, getBackupDirectory().toFile(),
"%world%-%id%-" + System.currentTimeMillis(), () -> "%world%-%id%-" + System.currentTimeMillis(), () ->
future.complete(new Backup(this, System.currentTimeMillis(), null)) future.complete(new Backup(this, System.currentTimeMillis(), null))
); );
@@ -180,7 +184,7 @@ public class PlayerBackupProfile implements BackupProfile {
try { try {
schematic = this.schematicHandler.getSchematic(backup.getFile().toFile()); schematic = this.schematicHandler.getSchematic(backup.getFile().toFile());
} catch (SchematicHandler.UnsupportedFormatException e) { } catch (SchematicHandler.UnsupportedFormatException e) {
e.printStackTrace(); LOGGER.error("Unsupported format for backup {}", backup.getFile(), e);
} }
if (schematic == null) { if (schematic == null) {
future.completeExceptionally(new IllegalArgumentException( future.completeExceptionally(new IllegalArgumentException(
@@ -200,10 +204,9 @@ public class PlayerBackupProfile implements BackupProfile {
if (value) { if (value) {
future.complete(null); future.complete(null);
} else { } else {
future.completeExceptionally(new RuntimeException(MINI_MESSAGE.escapeTags( future.completeExceptionally(new PlotSquaredException(
TranslatableCaption TranslatableCaption
.of("schematics.schematic_paste_failed") .of("schematics.schematic_paste_failed")));
.getComponent(ConsolePlayer.getConsole()))));
} }
} }
} }

View File

@@ -32,6 +32,8 @@ import com.plotsquared.core.util.task.TaskManager;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
@@ -47,6 +49,7 @@ import java.util.concurrent.TimeUnit;
@Singleton @Singleton
public class SimpleBackupManager implements BackupManager { public class SimpleBackupManager implements BackupManager {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + SimpleBackupManager.class.getSimpleName());
private final Path backupPath; private final Path backupPath;
private final boolean automaticBackup; private final boolean automaticBackup;
private final int backupLimit; private final int backupLimit;
@@ -112,7 +115,12 @@ public class SimpleBackupManager implements BackupManager {
TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage())))
); );
} }
throwable.printStackTrace(); LOGGER.error(
"Error creating backup for plot {};{} and player {}",
plot.getArea(),
plot.getId(),
player == null ? "null" : player.getName(), throwable
);
} else { } else {
if (player != null) { if (player != null) {
player.sendMessage(TranslatableCaption.of("backups.backup_automatic_finished")); player.sendMessage(TranslatableCaption.of("backups.backup_automatic_finished"));
@@ -128,6 +136,7 @@ public class SimpleBackupManager implements BackupManager {
return this.automaticBackup; return this.automaticBackup;
} }
@NonNull
public Path getBackupPath() { public Path getBackupPath() {
return this.backupPath; return this.backupPath;
} }

View File

@@ -24,6 +24,7 @@ import com.plotsquared.core.backup.BackupProfile;
import com.plotsquared.core.backup.NullBackupProfile; import com.plotsquared.core.backup.NullBackupProfile;
import com.plotsquared.core.backup.PlayerBackupProfile; import com.plotsquared.core.backup.PlayerBackupProfile;
import com.plotsquared.core.configuration.caption.TranslatableCaption; import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.exception.PlotSquaredException;
import com.plotsquared.core.permissions.Permission; import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.PlotPlayer; import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot; import com.plotsquared.core.plot.Plot;
@@ -32,6 +33,8 @@ import com.plotsquared.core.util.task.RunnableVal3;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag; import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import java.nio.file.Files; import java.nio.file.Files;
@@ -57,6 +60,8 @@ import java.util.stream.Stream;
permission = "plots.backup") permission = "plots.backup")
public final class Backup extends Command { public final class Backup extends Command {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + Backup.class.getSimpleName());
private final BackupManager backupManager; private final BackupManager backupManager;
@Inject @Inject
@@ -326,19 +331,28 @@ public final class Backup extends Command {
if (backupProfile instanceof NullBackupProfile) { if (backupProfile instanceof NullBackupProfile) {
player.sendMessage( player.sendMessage(
TranslatableCaption.of("backups.backup_impossible"), TranslatableCaption.of("backups.backup_impossible"),
TagResolver.resolver("plot", Tag.inserting( TagResolver.resolver(
"plot", Tag.inserting(
TranslatableCaption.of("generic.generic_other").toComponent(player) TranslatableCaption.of("generic.generic_other").toComponent(player)
)) )
)
); );
} else { } else {
backupProfile.listBackups().whenComplete((backups, throwable) -> { backupProfile.listBackups().whenComplete((backups, throwable) -> {
if (throwable != null) { if (throwable != null) {
Component reason;
if (throwable instanceof PlotSquaredException pe) {
reason = pe.getCaption().toComponent(player);
} else {
reason = Component.text(throwable.getMessage());
}
player.sendMessage( player.sendMessage(
TranslatableCaption.of("backups.backup_load_failure"), TranslatableCaption.of("backups.backup_load_failure"),
TagResolver.resolver("reason", Tag.inserting(Component.text(throwable.getMessage()))) TagResolver.resolver("reason", Tag.inserting(reason))
); );
throwable.printStackTrace(); LOGGER.error("Error loading player ({}) backup", player.getName(), throwable);
} else { return;
}
if (number < 1 || number > backups.size()) { if (number < 1 || number > backups.size()) {
player.sendMessage( player.sendMessage(
TranslatableCaption.of("backups.backup_impossible"), TranslatableCaption.of("backups.backup_impossible"),
@@ -364,7 +378,8 @@ public final class Backup extends Command {
) )
); );
} else { } else {
CmdConfirm.addPending(player, "/plot backup load " + number, CmdConfirm.addPending(
player, "/plot backup load " + number,
() -> backupProfile.restoreBackup(backup, player) () -> backupProfile.restoreBackup(backup, player)
.whenComplete((n, error) -> { .whenComplete((n, error) -> {
if (error != null) { if (error != null) {
@@ -382,7 +397,6 @@ public final class Backup extends Command {
); );
} }
} }
}
}); });
} }
} }

View File

@@ -0,0 +1,62 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.exception;
import com.plotsquared.core.configuration.caption.Caption;
import com.plotsquared.core.configuration.caption.LocaleHolder;
/**
* Internal use only. Used to allow adventure captions to be used in an exception
*
* @since TODO
*/
public final class PlotSquaredException extends RuntimeException {
private final Caption caption;
/**
* Create a new instance with the given caption
*
* @param caption caption
*/
public PlotSquaredException(Caption caption) {
this.caption = caption;
}
/**
* Create a new instance with the given caption and cause
*
* @param caption caption
* @param cause cause
*/
public PlotSquaredException(Caption caption, Exception cause) {
super(cause);
this.caption = caption;
}
@Override
public String getMessage() {
return caption.getComponent(LocaleHolder.console());
}
public Caption getCaption() {
return caption;
}
}

View File

@@ -135,6 +135,7 @@ public abstract class SchematicHandler {
} }
final String filename; final String filename;
final String website; final String website;
final @Nullable UUID finalUuid = uuid;
if (uuid == null) { if (uuid == null) {
uuid = UUID.randomUUID(); uuid = UUID.randomUUID();
website = Settings.Web.URL + "upload.php?" + uuid; website = Settings.Web.URL + "upload.php?" + uuid;
@@ -144,10 +145,11 @@ public abstract class SchematicHandler {
filename = file + '.' + extension; filename = file + '.' + extension;
} }
final URL url; final URL url;
String uri = Settings.Web.URL + "?key=" + uuid + "&type=" + extension;
try { try {
url = URI.create(Settings.Web.URL + "?key=" + uuid + "&type=" + extension).toURL(); url = URI.create(uri).toURL();
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
e.printStackTrace(); LOGGER.error("Malformed URI `{}`", uri, e);
whenDone.run(); whenDone.run();
return; return;
} }
@@ -193,7 +195,7 @@ public abstract class SchematicHandler {
} }
TaskManager.runTask(whenDone); TaskManager.runTask(whenDone);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error while uploading schematic for UUID {}", finalUuid, e);
TaskManager.runTask(whenDone); TaskManager.runTask(whenDone);
} }
}); });
@@ -388,8 +390,14 @@ public abstract class SchematicHandler {
} }
queue.enqueue(); queue.enqueue();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
TaskManager.runTask(whenDone); TaskManager.runTask(whenDone);
LOGGER.error(
"Error pasting schematic to plot {};{} for player {}",
plot.getArea(),
plot.getId(),
actor == null ? "null" : actor.getName(),
e
);
} }
} }
@@ -456,7 +464,7 @@ public abstract class SchematicHandler {
Clipboard clip = reader.read(); Clipboard clip = reader.read();
return new Schematic(clip); return new Schematic(clip);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error reading schematic from file {}", file.getAbsolutePath(), e);
} }
} else { } else {
throw new UnsupportedFormatException("This schematic format is not recognised or supported."); throw new UnsupportedFormatException("This schematic format is not recognised or supported.");
@@ -470,7 +478,7 @@ public abstract class SchematicHandler {
InputStream inputStream = Channels.newInputStream(readableByteChannel); InputStream inputStream = Channels.newInputStream(readableByteChannel);
return getSchematic(inputStream); return getSchematic(inputStream);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error reading schematic from {}", url, e);
} }
return null; return null;
} }
@@ -486,7 +494,7 @@ public abstract class SchematicHandler {
Clipboard clip = schematicReader.read(); Clipboard clip = schematicReader.read();
return new Schematic(clip); return new Schematic(clip);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error reading schematic", e);
} }
} }
return null; return null;
@@ -515,7 +523,7 @@ public abstract class SchematicHandler {
} }
return schematics; return schematics;
} catch (JsonParseException | IOException e) { } catch (JsonParseException | IOException e) {
e.printStackTrace(); LOGGER.error("Error retrieving saves for UUID {}", uuid, e);
} }
return null; return null;
} }
@@ -532,7 +540,7 @@ public abstract class SchematicHandler {
try (NBTOutputStream nos = new NBTOutputStream(new GZIPOutputStream(output, true))) { try (NBTOutputStream nos = new NBTOutputStream(new GZIPOutputStream(output, true))) {
nos.writeNamedTag("Schematic", tag); nos.writeNamedTag("Schematic", tag);
} catch (IOException e1) { } catch (IOException e1) {
e1.printStackTrace(); LOGGER.error("Error uploading schematic for UUID {}", uuid, e1);
} }
} }
}, whenDone); }, whenDone);
@@ -556,9 +564,9 @@ public abstract class SchematicHandler {
nbtStream.writeNamedTag("Schematic", tag); nbtStream.writeNamedTag("Schematic", tag);
} }
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
e.printStackTrace(); LOGGER.error("Error saving schematic at {}", path, e);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); LOGGER.error("Error saving schematic at {}", path, e);
return false; return false;
} }
return true; return true;
@@ -581,7 +589,7 @@ public abstract class SchematicHandler {
schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray())); schematic.put("BlockData", new ByteArrayTag(buffer.toByteArray()));
schematic.put("BlockEntities", new ListTag(CompoundTag.class, tileEntities)); schematic.put("BlockEntities", new ListTag(CompoundTag.class, tileEntities));
if (biomeBuffer.size() == 0 || biomePalette.size() == 0) { if (biomeBuffer.size() == 0 || biomePalette.isEmpty()) {
return; return;
} }
@@ -733,10 +741,7 @@ public abstract class SchematicHandler {
} }
BaseBlock block = aabb.getWorld().getFullBlock(point); BaseBlock block = aabb.getWorld().getFullBlock(point);
if (block.getNbtData() != null) { if (block.getNbtData() != null) {
Map<String, Tag> values = new HashMap<>(); Map<String, Tag> values = new HashMap<>(block.getNbtData().getValue());
for (Map.Entry<String, Tag> entry : block.getNbtData().getValue().entrySet()) {
values.put(entry.getKey(), entry.getValue());
}
// Positions are kept in NBT, we don't want that. // Positions are kept in NBT, we don't want that.
values.remove("x"); values.remove("x");