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) {
|
public void onJoin(PlayerJoinEvent event) {
|
||||||
Player player = event.getPlayer();
|
Player player = event.getPlayer();
|
||||||
player.discoverRecipes(SurvivalRecipes.allKeys());
|
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());
|
int films = Main.getInstance().getPendingPhotos().takeRefunds(player.getUniqueId());
|
||||||
for (int i = 0; i < films; i++) CameraItems.give(player, CameraItems.createFilm());
|
for (int i = 0; i < films; i++) CameraItems.give(player, CameraItems.createFilm());
|
||||||
if (films > 0) {
|
if (films > 0) {
|
||||||
|
|||||||
@@ -2,11 +2,20 @@ package eu.mhsl.minecraft.pixelpics.survival;
|
|||||||
|
|
||||||
import eu.mhsl.minecraft.pixelpics.Main;
|
import eu.mhsl.minecraft.pixelpics.Main;
|
||||||
import eu.mhsl.minecraft.pixelpics.utils.MapManager;
|
import eu.mhsl.minecraft.pixelpics.utils.MapManager;
|
||||||
|
import eu.mhsl.minecraft.pixelpics.utils.MapUsageScan;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
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 java.util.UUID;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.ConfigurationSection;
|
import org.bukkit.configuration.ConfigurationSection;
|
||||||
import org.bukkit.configuration.file.YamlConfiguration;
|
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
|
* 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);
|
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() {
|
public void recoverAbandoned() {
|
||||||
ConfigurationSection jobs = yml.getConfigurationSection(JOBS);
|
ConfigurationSection jobs = yml.getConfigurationSection(JOBS);
|
||||||
if (jobs == null) return;
|
if (jobs == null) return;
|
||||||
int recovered = 0;
|
Map<Integer, UUID> owed = new LinkedHashMap<>();
|
||||||
for (String key : jobs.getKeys(false)) {
|
for (String key : jobs.getKeys(false)) {
|
||||||
ConfigurationSection job = jobs.getConfigurationSection(key);
|
ConfigurationSection job = jobs.getConfigurationSection(key);
|
||||||
if (job == null) continue;
|
int mapId;
|
||||||
int mapId = Integer.parseInt(key);
|
try {
|
||||||
if (MapManager.hasImage(mapId)) continue; // developed, only the bookkeeping was lost
|
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);
|
if (!MapManager.isManaged(mapId)) MapManager.savePlaceholder(mapId);
|
||||||
String player = job.getString("player");
|
String player = job.getString("player");
|
||||||
if (player != null && job.getBoolean("film")) {
|
if (player == null || !job.getBoolean("film")) continue;
|
||||||
addRefund(UUID.fromString(player));
|
try {
|
||||||
recovered++;
|
owed.put(mapId, UUID.fromString(player));
|
||||||
|
} catch (IllegalArgumentException ignored) {
|
||||||
|
// corrupt entry: nothing to refund
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
yml.set(JOBS, null);
|
if (owed.isEmpty()) {
|
||||||
save();
|
yml.set(JOBS, null);
|
||||||
if (recovered > 0) {
|
save();
|
||||||
Main.getInstance()
|
return;
|
||||||
.getLogger()
|
|
||||||
.warning(recovered + " photo(s) did not develop before the last shutdown; their film is refunded"
|
|
||||||
+ " when the players join.");
|
|
||||||
}
|
}
|
||||||
|
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) {
|
public void add(int mapId, UUID player, boolean film) {
|
||||||
@@ -66,9 +122,13 @@ public final class PendingPhotos {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addRefund(UUID player) {
|
public void addRefund(UUID player) {
|
||||||
|
addRefund(player, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRefund(UUID player, boolean save) {
|
||||||
String key = REFUNDS + "." + player;
|
String key = REFUNDS + "." + player;
|
||||||
yml.set(key, yml.getInt(key) + 1);
|
yml.set(key, yml.getInt(key) + 1);
|
||||||
save();
|
if (save) save();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The films owed to {@code player}, now cleared. */
|
/** The films owed to {@code player}, now cleared. */
|
||||||
|
|||||||
Reference in new issue
Block a user