photo storage failures handled: a shot is refused (film kept) while the image folder is not writable, a photo whose png cannot be saved fails like a render error (film back) instead of becoming a vanilla map after the restart, png/index files written atomically (tmp + move, no truncated png after a crash), one-line warnings instead of imageio stack traces, unreadable png warned

This commit is contained in:
MineTec committed 2026-11-05 17:35:00 +01:00
1 parent bf192588a9
commit c75c7a4b60
2 files changed
+63 -16

No files matched your search

@@ -53,6 +53,13 @@ public final class PhotoService {
return false; return false;
} }
if (!MapManager.canStore()) {
player.sendActionBar(Component.text(
"Fotos können gerade nicht gespeichert werden — bitte einem Admin melden.", NamedTextColor.RED));
plugin.getLogger().warning("Photo of " + player.getName() + " refused: the image folder is not writable.");
return false;
}
RenderManager manager = plugin.getRenderManager(); RenderManager manager = plugin.getRenderManager();
UUID user = player.getUniqueId(); UUID user = player.getUniqueId();
RenderManager.Outcome outcome = manager.tryReserve(user); RenderManager.Outcome outcome = manager.tryReserve(user);
@@ -118,7 +125,12 @@ public final class PhotoService {
}, },
out -> { out -> {
chunks.release(); // already released by prepare chunks.release(); // already released by prepare
MapManager.saveImage(out.image(), id); if (!MapManager.saveImage(out.image(), id)) {
failed(
id, user,
usesFilm); // not stored: it would be lost (or a vanilla map) after a restart
return;
}
MapManager.saveIndices(out.indices(), id); MapManager.saveIndices(out.indices(), id);
plugin.getPendingPhotos().remove(id); plugin.getPendingPhotos().remove(id);
mapRenderer.develop(out.indices()); mapRenderer.develop(out.indices());
@@ -2,9 +2,12 @@ package eu.mhsl.minecraft.pixelpics.utils;
import eu.mhsl.minecraft.pixelpics.Main; import eu.mhsl.minecraft.pixelpics.Main;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@@ -173,28 +176,58 @@ public class MapManager {
+ backup + "."); + backup + ".");
} }
/** Blank film indices for a photo that has not developed (yet): never a vanilla world map after a restart. */ /** Whether new photos can be stored: the folder exists (or was created) and is writable. */
public static void savePlaceholder(int id) { public static boolean canStore() {
byte[] blank = new byte[INDEX_BYTES]; return ensureFolder() && Files.isWritable(imageFolder().toPath());
Arrays.fill(blank, ImageMapRenderer.filmColor());
saveIndices(blank, id);
} }
public static void saveImage(BufferedImage image, int id) { /**
if (!ensureFolder()) return; * Blank film indices for a photo that has not developed (yet): never a vanilla world map after a restart. False if
* they could not be stored.
*/
public static boolean savePlaceholder(int id) {
byte[] blank = new byte[INDEX_BYTES];
Arrays.fill(blank, ImageMapRenderer.filmColor());
return saveIndices(blank, id);
}
/** Stores the PNG (atomically: a crash never leaves a truncated one); false if it could not be written. */
public static boolean saveImage(BufferedImage image, int id) {
if (!ensureFolder()) return false;
try { try {
ImageIO.write(image, "png", getImagePath(id)); ByteArrayOutputStream png = new ByteArrayOutputStream();
if (!ImageIO.write(image, "png", png)) throw new IOException("no PNG writer");
writeAtomically(getImagePath(id), png.toByteArray());
return true;
} catch (IOException e) { } catch (IOException e) {
Main.getInstance().getLogger().warning("Failed to save map image " + id + ": " + e.getMessage()); Main.getInstance().getLogger().warning("Failed to save map image " + id + ": " + e);
return false;
} }
} }
public static void saveIndices(byte[] indices, int id) { /** Stores the index cache (atomically); false if it could not be written. */
if (indices == null || indices.length != INDEX_BYTES || !ensureFolder()) return; public static boolean saveIndices(byte[] indices, int id) {
if (indices == null || indices.length != INDEX_BYTES || !ensureFolder()) return false;
try { try {
Files.write(getIndexPath(id).toPath(), indices); writeAtomically(getIndexPath(id), indices);
return true;
} catch (IOException e) { } catch (IOException e) {
Main.getInstance().getLogger().warning("Failed to save map index cache " + id + ": " + e.getMessage()); Main.getInstance().getLogger().warning("Failed to save map index cache " + id + ": " + e);
return false;
}
}
private static void writeAtomically(File target, byte[] data) throws IOException {
Path tmp = target.toPath().resolveSibling(target.getName() + ".tmp");
try {
Files.write(tmp, data);
try {
Files.move(tmp, target.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
} catch (AtomicMoveNotSupportedException e) {
Files.move(tmp, target.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} finally {
Files.deleteIfExists(tmp);
} }
} }
@@ -215,8 +248,10 @@ public class MapManager {
File path = getImagePath(id); File path = getImagePath(id);
if (!path.isFile()) return null; if (!path.isFile()) return null;
try { try {
return ImageIO.read(path); BufferedImage image = ImageIO.read(path);
} catch (IOException e) { if (image == null) Main.getInstance().getLogger().warning("Map image " + id + " is no readable image.");
return image;
} catch (IOException | RuntimeException e) {
Main.getInstance().getLogger().warning("Failed to read map image " + id + ": " + e.getMessage()); Main.getInstance().getLogger().warning("Failed to read map image " + id + ": " + e.getMessage());
return null; return null;
} }