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;
}
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();
UUID user = player.getUniqueId();
RenderManager.Outcome outcome = manager.tryReserve(user);
@@ -118,7 +125,12 @@ public final class PhotoService {
},
out -> {
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);
plugin.getPendingPhotos().remove(id);
mapRenderer.develop(out.indices());
@@ -2,9 +2,12 @@ package eu.mhsl.minecraft.pixelpics.utils;
import eu.mhsl.minecraft.pixelpics.Main;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
@@ -173,28 +176,58 @@ public class MapManager {
+ backup + ".");
}
/** Blank film indices for a photo that has not developed (yet): never a vanilla world map after a restart. */
public static void savePlaceholder(int id) {
byte[] blank = new byte[INDEX_BYTES];
Arrays.fill(blank, ImageMapRenderer.filmColor());
saveIndices(blank, id);
/** Whether new photos can be stored: the folder exists (or was created) and is writable. */
public static boolean canStore() {
return ensureFolder() && Files.isWritable(imageFolder().toPath());
}
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 {
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) {
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) {
if (indices == null || indices.length != INDEX_BYTES || !ensureFolder()) return;
/** Stores the index cache (atomically); false if it could not be written. */
public static boolean saveIndices(byte[] indices, int id) {
if (indices == null || indices.length != INDEX_BYTES || !ensureFolder()) return false;
try {
Files.write(getIndexPath(id).toPath(), indices);
writeAtomically(getIndexPath(id), indices);
return true;
} 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);
if (!path.isFile()) return null;
try {
return ImageIO.read(path);
} catch (IOException e) {
BufferedImage image = ImageIO.read(path);
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());
return null;
}