crash recovery refunds only photos found in the saved data (async scan): after a crash that rolled the shot back to the last save the camera still had the film and the refund duplicated it, a failed scan refunds all, jobs stay in pending.yml until decided
This commit is contained in:
1 parent
3a064a5ffa
commit
bf192588a9
2 files changed
+81
-16
No files matched your search
@@ -18,6 +18,11 @@ public class JoinListener implements Listener {
|
||||
public void onJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
player.discoverRecipes(SurvivalRecipes.allKeys());
|
||||
giveRefunds(player);
|
||||
}
|
||||
|
||||
/** Hands out the film owed to {@code player} (also used once the crash recovery has decided). */
|
||||
static void giveRefunds(Player player) {
|
||||
int films = Main.getInstance().getPendingPhotos().takeRefunds(player.getUniqueId());
|
||||
for (int i = 0; i < films; i++) CameraItems.give(player, CameraItems.createFilm());
|
||||
if (films > 0) {
|
||||
|
||||
@@ -2,11 +2,20 @@ package eu.mhsl.minecraft.pixelpics.survival;
|
||||
|
||||
import eu.mhsl.minecraft.pixelpics.Main;
|
||||
import eu.mhsl.minecraft.pixelpics.utils.MapManager;
|
||||
import eu.mhsl.minecraft.pixelpics.utils.MapUsageScan;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.IllegalPluginAccessException;
|
||||
|
||||
/**
|
||||
* Persists photos that are still rendering ({@code pending.yml}), so a crash cannot swallow film: a job still listed
|
||||
@@ -27,31 +36,78 @@ public final class PendingPhotos {
|
||||
this.yml = YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
/** Turns jobs left over from a crash into film refunds; their maps keep the blank placeholder. */
|
||||
/**
|
||||
* Turns jobs left over from a crash into film refunds; their maps keep the blank placeholder. A crash also rolls
|
||||
* the world back to its last save: if that save predates the shot, the photo item is in no saved data and the
|
||||
* camera still holds the film, so a refund would duplicate it. The saved worlds and player files are therefore
|
||||
* scanned (off the main thread) and only photos found there are refunded; if the scan fails, all are refunded. The
|
||||
* jobs stay in the file until decided, so another crash meanwhile loses nothing.
|
||||
*/
|
||||
public void recoverAbandoned() {
|
||||
ConfigurationSection jobs = yml.getConfigurationSection(JOBS);
|
||||
if (jobs == null) return;
|
||||
int recovered = 0;
|
||||
Map<Integer, UUID> owed = new LinkedHashMap<>();
|
||||
for (String key : jobs.getKeys(false)) {
|
||||
ConfigurationSection job = jobs.getConfigurationSection(key);
|
||||
if (job == null) continue;
|
||||
int mapId = Integer.parseInt(key);
|
||||
if (MapManager.hasImage(mapId)) continue; // developed, only the bookkeeping was lost
|
||||
int mapId;
|
||||
try {
|
||||
mapId = Integer.parseInt(key);
|
||||
} catch (NumberFormatException e) {
|
||||
continue;
|
||||
}
|
||||
if (job == null || MapManager.hasImage(mapId)) continue; // developed, only the bookkeeping was lost
|
||||
if (!MapManager.isManaged(mapId)) MapManager.savePlaceholder(mapId);
|
||||
String player = job.getString("player");
|
||||
if (player != null && job.getBoolean("film")) {
|
||||
addRefund(UUID.fromString(player));
|
||||
recovered++;
|
||||
if (player == null || !job.getBoolean("film")) continue;
|
||||
try {
|
||||
owed.put(mapId, UUID.fromString(player));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// corrupt entry: nothing to refund
|
||||
}
|
||||
}
|
||||
yml.set(JOBS, null);
|
||||
save();
|
||||
if (recovered > 0) {
|
||||
Main.getInstance()
|
||||
.getLogger()
|
||||
.warning(recovered + " photo(s) did not develop before the last shutdown; their film is refunded"
|
||||
+ " when the players join.");
|
||||
if (owed.isEmpty()) {
|
||||
yml.set(JOBS, null);
|
||||
save();
|
||||
return;
|
||||
}
|
||||
Main plugin = Main.getInstance();
|
||||
List<Path> worlds = MapUsageScan.worldFolders();
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Set<Integer> saved;
|
||||
try {
|
||||
saved = MapUsageScan.disk(worlds);
|
||||
} catch (IOException | RuntimeException e) {
|
||||
plugin.getLogger()
|
||||
.warning("Could not scan the saved data for photos lost in the crash (" + e.getMessage()
|
||||
+ "); refunding all of their film.");
|
||||
saved = null;
|
||||
}
|
||||
Set<Integer> found = saved;
|
||||
try {
|
||||
Bukkit.getScheduler().runTask(plugin, () -> refundAbandoned(owed, found));
|
||||
} catch (IllegalPluginAccessException ignored) {
|
||||
// disabled meanwhile: the jobs are still in the file, decided at the next start
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Refunds the abandoned photos whose item was saved ({@code saved} null = all). */
|
||||
private void refundAbandoned(Map<Integer, UUID> owed, Set<Integer> saved) {
|
||||
int refunded = 0;
|
||||
for (Map.Entry<Integer, UUID> e : owed.entrySet()) {
|
||||
if (saved == null || saved.contains(e.getKey())) {
|
||||
addRefund(e.getValue(), false);
|
||||
refunded++;
|
||||
}
|
||||
yml.set(JOBS + "." + e.getKey(), null);
|
||||
}
|
||||
save();
|
||||
Main.getInstance()
|
||||
.getLogger()
|
||||
.warning(owed.size() + " photo(s) did not develop before the last shutdown; " + refunded
|
||||
+ " film refunded when the players join, " + (owed.size() - refunded)
|
||||
+ " not (the crash rolled the shot back: the photo was never saved).");
|
||||
for (Player player : Bukkit.getOnlinePlayers()) JoinListener.giveRefunds(player);
|
||||
}
|
||||
|
||||
public void add(int mapId, UUID player, boolean film) {
|
||||
@@ -66,9 +122,13 @@ public final class PendingPhotos {
|
||||
}
|
||||
|
||||
public void addRefund(UUID player) {
|
||||
addRefund(player, true);
|
||||
}
|
||||
|
||||
private void addRefund(UUID player, boolean save) {
|
||||
String key = REFUNDS + "." + player;
|
||||
yml.set(key, yml.getInt(key) + 1);
|
||||
save();
|
||||
if (save) save();
|
||||
}
|
||||
|
||||
/** The films owed to {@code player}, now cleared. */
|
||||
|
||||
Reference in new issue
Block a user