Rename Favs jar and print error on failed load
LocalSession history on disk size limit per player (in MB)
Possible fix for ForgeEssentials incompatibility
This commit is contained in:
Jesse Boyd 2016-08-11 21:34:56 +10:00
parent 3ec42144e7
commit d0b5dab2a0
9 changed files with 168 additions and 21 deletions

View File

@ -71,8 +71,11 @@ public class Settings extends Config {
"NoteBlock, Sign, Skull, Structure" "NoteBlock, Sign, Skull, Structure"
}) })
public int MAX_BLOCKSTATES = 1337; public int MAX_BLOCKSTATES = 1337;
@Comment("Maximum size of the player's history in Megabytes") @Comment({
public int MAX_HISTORY_MB = 200; "Maximum size of the player's history in Megabytes:",
" - History on disk or memory will be deleted",
})
public int MAX_HISTORY_MB = -1;
} }
public static class HISTORY { public static class HISTORY {
@ -266,7 +269,7 @@ public class Settings extends Config {
limit.MAX_ENTITIES = Math.max(limit.MAX_ENTITIES, newLimit.MAX_ENTITIES != -1 ? newLimit.MAX_ENTITIES : Integer.MAX_VALUE); limit.MAX_ENTITIES = Math.max(limit.MAX_ENTITIES, newLimit.MAX_ENTITIES != -1 ? newLimit.MAX_ENTITIES : Integer.MAX_VALUE);
limit.MAX_FAILS = Math.max(limit.MAX_FAILS, newLimit.MAX_FAILS != -1 ? newLimit.MAX_FAILS : Integer.MAX_VALUE); limit.MAX_FAILS = Math.max(limit.MAX_FAILS, newLimit.MAX_FAILS != -1 ? newLimit.MAX_FAILS : Integer.MAX_VALUE);
limit.MAX_ITERATIONS = Math.max(limit.MAX_ITERATIONS, newLimit.MAX_ITERATIONS != -1 ? newLimit.MAX_ITERATIONS : Integer.MAX_VALUE); limit.MAX_ITERATIONS = Math.max(limit.MAX_ITERATIONS, newLimit.MAX_ITERATIONS != -1 ? newLimit.MAX_ITERATIONS : Integer.MAX_VALUE);
limit.MAX_HISTORY = HISTORY.USE_DISK ? Integer.MAX_VALUE : Math.max(limit.MAX_HISTORY, newLimit.MAX_HISTORY_MB != -1 ? newLimit.MAX_HISTORY_MB : Integer.MAX_VALUE); limit.MAX_HISTORY = Math.max(limit.MAX_HISTORY, newLimit.MAX_HISTORY_MB != -1 ? newLimit.MAX_HISTORY_MB : Integer.MAX_VALUE);
} }
} }
return limit; return limit;

View File

@ -35,6 +35,7 @@ public class RollbackDatabase {
private String GET_EDITS; private String GET_EDITS;
private String GET_EDITS_USER; private String GET_EDITS_USER;
private String DELETE_EDITS_USER; private String DELETE_EDITS_USER;
private String DELETE_EDIT_USER;
private String PURGE; private String PURGE;
private ConcurrentLinkedQueue<RollbackOptimizedHistory> historyChanges = new ConcurrentLinkedQueue<>(); private ConcurrentLinkedQueue<RollbackOptimizedHistory> historyChanges = new ConcurrentLinkedQueue<>();
@ -52,6 +53,7 @@ public class RollbackDatabase {
GET_EDITS = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? ORDER BY `time` DESC, `id` DESC"; GET_EDITS = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? ORDER BY `time` DESC, `id` DESC";
GET_EDITS_USER = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=? ORDER BY `time` DESC, `id` DESC"; GET_EDITS_USER = "SELECT `player`,`id` FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=? ORDER BY `time` DESC, `id` DESC";
DELETE_EDITS_USER = "DELETE FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=?"; DELETE_EDITS_USER = "DELETE FROM `" + prefix + "edits` WHERE `x2`>=? AND `x1`<=? AND `y2`>=? AND `y1`<=? AND `z2`>=? AND `z1`<=? AND `time`>? AND `player`=?";
DELETE_EDIT_USER = "DELETE FROM `" + prefix + "edits` WHERE `player`=? AND `id`=?";
init(); init();
purge((int) TimeUnit.DAYS.toMillis(Settings.HISTORY.DELETE_AFTER_DAYS)); purge((int) TimeUnit.DAYS.toMillis(Settings.HISTORY.DELETE_AFTER_DAYS));
TaskManager.IMP.async(new Runnable() { TaskManager.IMP.async(new Runnable() {
@ -90,6 +92,21 @@ public class RollbackDatabase {
notify.add(run); notify.add(run);
} }
public void delete(final UUID uuid, final int id) {
addTask(new Runnable() {
@Override
public void run() {
try (PreparedStatement stmt = connection.prepareStatement(DELETE_EDIT_USER)) {
byte[] uuidBytes = ByteBuffer.allocate(16).putLong(uuid.getMostSignificantBits()).putLong(uuid.getLeastSignificantBits()).array();
stmt.setBytes(1, uuidBytes);
stmt.setInt(2, id);
} catch (SQLException e) {
e.printStackTrace();
}
}
});
}
public void purge(int diff) { public void purge(int diff) {
long now = System.currentTimeMillis() / 1000; long now = System.currentTimeMillis() / 1000;
final int then = (int) (now - diff); final int then = (int) (now - diff);

View File

@ -47,7 +47,7 @@ public class FaweLimit {
MAX.MAX_ITERATIONS = Integer.MAX_VALUE; MAX.MAX_ITERATIONS = Integer.MAX_VALUE;
MAX.MAX_BLOCKSTATES = Integer.MAX_VALUE; MAX.MAX_BLOCKSTATES = Integer.MAX_VALUE;
MAX.MAX_ENTITIES = Integer.MAX_VALUE; MAX.MAX_ENTITIES = Integer.MAX_VALUE;
MAX.MAX_HISTORY = 15; MAX.MAX_HISTORY = Integer.MAX_VALUE;
} }
public boolean MAX_CHANGES() { public boolean MAX_CHANGES() {

View File

@ -2,6 +2,8 @@ package com.boydti.fawe.object.changeset;
import com.boydti.fawe.Fawe; import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.Settings; import com.boydti.fawe.config.Settings;
import com.boydti.fawe.database.DBHandler;
import com.boydti.fawe.database.RollbackDatabase;
import com.boydti.fawe.object.FaweInputStream; import com.boydti.fawe.object.FaweInputStream;
import com.boydti.fawe.object.IntegerPair; import com.boydti.fawe.object.IntegerPair;
import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RegionWrapper;
@ -101,6 +103,15 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
initFiles(folder); initFiles(folder);
} }
public void delete() {
Fawe.debug("Deleting history: " + Fawe.imp().getWorldName(getWorld()) + "/" + uuid + "/" + index);
deleteFiles();
if (Settings.HISTORY.USE_DATABASE) {
RollbackDatabase db = DBHandler.IMP.getDatabase(Fawe.imp().getWorldName(getWorld()));
db.delete(uuid, index);
}
}
public void deleteFiles() { public void deleteFiles() {
bdFile.delete(); bdFile.delete();
nbtfFile.delete(); nbtfFile.delete();
@ -166,19 +177,19 @@ public class DiskStorageHistory extends FaweStreamChangeSet {
public long getSizeOnDisk() { public long getSizeOnDisk() {
int total = 0; int total = 0;
if (bdFile.exists()) { if (bdFile.exists()) {
total += bdFile.getTotalSpace(); total += bdFile.length();
} }
if (nbtfFile.exists()) { if (nbtfFile.exists()) {
total += entfFile.getTotalSpace(); total += entfFile.length();
} }
if (nbttFile.exists()) { if (nbttFile.exists()) {
total += entfFile.getTotalSpace(); total += entfFile.length();
} }
if (entfFile.exists()) { if (entfFile.exists()) {
total += entfFile.getTotalSpace(); total += entfFile.length();
} }
if (enttFile.exists()) { if (enttFile.exists()) {
total += entfFile.getTotalSpace(); total += entfFile.length();
} }
return total; return total;
} }

View File

@ -80,6 +80,8 @@ public abstract class FaweChangeSet implements ChangeSet {
public abstract void addEntityCreate(CompoundTag tag); public abstract void addEntityCreate(CompoundTag tag);
public abstract Iterator<Change> getIterator(boolean redo); public abstract Iterator<Change> getIterator(boolean redo);
public void delete() {};
public EditSession toEditSession(FawePlayer player) { public EditSession toEditSession(FawePlayer player) {
EditSession editSession = new EditSessionBuilder(world).player(player).autoQueue(false).fastmode(false).checkMemory(false).changeSet(this).allowedRegions(RegionWrapper.GLOBAL().toArray()).build(); EditSession editSession = new EditSessionBuilder(world).player(player).autoQueue(false).fastmode(false).checkMemory(false).changeSet(this).allowedRegions(RegionWrapper.GLOBAL().toArray()).build();
editSession.setSize(1); editSession.setSize(1);

View File

@ -8,6 +8,7 @@ import com.boydti.fawe.object.FaweOutputStream;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper; import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal; import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.changeset.CPUOptimizedChangeSet; import com.boydti.fawe.object.changeset.CPUOptimizedChangeSet;
import com.boydti.fawe.object.changeset.FaweStreamChangeSet; import com.boydti.fawe.object.changeset.FaweStreamChangeSet;
import com.sk89q.jnbt.CompoundTag; import com.sk89q.jnbt.CompoundTag;
@ -38,12 +39,18 @@ import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicLong;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -78,6 +85,42 @@ public class MainUtil {
Fawe.debug(s); Fawe.debug(s);
} }
public static long getTotalSize(Path path) {
final AtomicLong size = new AtomicLong(0);
traverse(path, new RunnableVal2<Path, BasicFileAttributes>() {
@Override
public void run(Path path, BasicFileAttributes attrs) {
size.addAndGet (attrs.size());
}
});
return size.get();
}
public static long traverse(Path path, final RunnableVal2<Path, BasicFileAttributes> onEach) {
final AtomicLong size = new AtomicLong(0);
try {
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
@Override public FileVisitResult
visitFile(Path file, BasicFileAttributes attrs) {
onEach.run(file, attrs);
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult
visitFileFailed(Path file, IOException exc) {
return FileVisitResult.CONTINUE;
}
@Override public FileVisitResult
postVisitDirectory (Path dir, IOException exc) {
return FileVisitResult.CONTINUE;
}
});
}
catch (IOException e) {
throw new AssertionError ("walkFileTree will not throw IOException if the FileVisitor does not");
}
return size.get();
}
public static File getFile(File base, String path) { public static File getFile(File base, String path) {
if (Paths.get(path).isAbsolute()) { if (Paths.get(path).isAbsolute()) {
return new File(path); return new File(path);
@ -93,9 +136,10 @@ public class MainUtil {
return getCompressedOS(os, Settings.HISTORY.COMPRESSION_LEVEL); return getCompressedOS(os, Settings.HISTORY.COMPRESSION_LEVEL);
} }
public static long getSizeInMemory(ChangeSet changeSet) { public static long getSize(ChangeSet changeSet) {
if (changeSet instanceof FaweStreamChangeSet){ if (changeSet instanceof FaweStreamChangeSet){
return 92 + ((FaweStreamChangeSet) changeSet).getSizeInMemory(); FaweStreamChangeSet fscs = (FaweStreamChangeSet) changeSet;
return fscs.getSizeOnDisk() + fscs.getSizeInMemory();
} else if (changeSet instanceof CPUOptimizedChangeSet) { } else if (changeSet instanceof CPUOptimizedChangeSet) {
return changeSet.size() + 32; return changeSet.size() + 32;
} else if (changeSet != null) { } else if (changeSet != null) {

View File

@ -24,11 +24,13 @@ import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.FaweInputStream; import com.boydti.fawe.object.FaweInputStream;
import com.boydti.fawe.object.FaweOutputStream; import com.boydti.fawe.object.FaweOutputStream;
import com.boydti.fawe.object.FawePlayer; import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RunnableVal2;
import com.boydti.fawe.object.changeset.DiskStorageHistory; import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.object.changeset.FaweChangeSet; import com.boydti.fawe.object.changeset.FaweChangeSet;
import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard; import com.boydti.fawe.object.clipboard.DiskOptimizedClipboard;
import com.boydti.fawe.util.EditSessionBuilder; import com.boydti.fawe.util.EditSessionBuilder;
import com.boydti.fawe.util.MainUtil; import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.wrappers.WorldWrapper;
import com.sk89q.jchronic.Chronic; import com.sk89q.jchronic.Chronic;
import com.sk89q.jchronic.Options; import com.sk89q.jchronic.Options;
import com.sk89q.jchronic.utils.Span; import com.sk89q.jchronic.utils.Span;
@ -61,6 +63,10 @@ import java.io.FileFilter;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
@ -72,6 +78,7 @@ import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.swing.filechooser.FileNameExtensionFilter; import javax.swing.filechooser.FileNameExtensionFilter;
@ -118,7 +125,6 @@ public class LocalSession {
} }
}); });
private transient volatile Integer historyNegativeIndex; private transient volatile Integer historyNegativeIndex;
private transient volatile long historySize = 0;
private transient ClipboardHolder clipboard; private transient ClipboardHolder clipboard;
private transient boolean toolControl = true; private transient boolean toolControl = true;
private transient boolean superPickaxe = false; private transient boolean superPickaxe = false;
@ -133,9 +139,9 @@ public class LocalSession {
private transient Mask mask; private transient Mask mask;
private transient TimeZone timezone = TimeZone.getDefault(); private transient TimeZone timezone = TimeZone.getDefault();
// May be null
private transient World currentWorld; private transient World currentWorld;
private transient UUID uuid; private transient UUID uuid;
private transient volatile long historySize = 0;
// Saved properties // Saved properties
private String lastScript; private String lastScript;
@ -222,16 +228,70 @@ public class LocalSession {
} }
}); });
} }
historySize = 0;
if (editIds.size() > 0) { if (editIds.size() > 0) {
historySize = MainUtil.getTotalSize(folder.toPath());
Collections.sort(editIds); Collections.sort(editIds);
for (int index : editIds) { for (int index : editIds) {
history.add(index); history.add(index);
} }
} else {
historySize = 0;
} }
return editIds.size() > 0; return editIds.size() > 0;
} }
@Deprecated
private void deleteOldFiles(UUID uuid, World world, long maxBytes) throws IOException {
final File folder = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.PATHS.HISTORY + File.separator + Fawe.imp().getWorldName(world) + File.separator + uuid);
final ArrayList<Integer> ids = new ArrayList<Integer>();
final HashMap<Integer, ArrayDeque<Path>> paths = new HashMap<>();
final HashMap<Integer, AtomicLong> sizes = new HashMap<>();
final AtomicLong totalSize = new AtomicLong();
MainUtil.traverse(folder.toPath(), new RunnableVal2<Path, BasicFileAttributes>() {
@Override
public void run(Path path, BasicFileAttributes attr) {
try {
String file = path.getFileName().toString();
int index = file.indexOf('.');
if (index == -1) {
return;
}
int id = Integer.parseInt(file.substring(0, index));
long size = attr.size();
totalSize.addAndGet(size);
ArrayDeque<Path> existingPaths = paths.get(id);
if (existingPaths == null) {
existingPaths = new ArrayDeque<Path>();
paths.put(id, existingPaths);
}
existingPaths.add(path);
AtomicLong existingSize = sizes.get(id);
if (existingSize == null) {
existingSize = new AtomicLong();
sizes.put(id, existingSize);
}
existingSize.addAndGet(size);
} catch (NumberFormatException ignore){
ignore.printStackTrace();
}
}
});
if (totalSize.get() < maxBytes) {
return;
}
Collections.sort(ids);
long total = totalSize.get();
for (int i = 0; i < ids.size() && total > maxBytes; i++) {
int id = ids.get(i);
for (Path path : paths.get(id)) {
Files.delete(path);
}
total -= sizes.get(id).get();
}
}
private void loadHistoryNegativeIndex(UUID uuid, World world) { private void loadHistoryNegativeIndex(UUID uuid, World world) {
File file = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.PATHS.HISTORY + File.separator + Fawe.imp().getWorldName(world) + File.separator + uuid + File.separator + "index"); File file = MainUtil.getFile(Fawe.imp().getDirectory(), Settings.PATHS.HISTORY + File.separator + Fawe.imp().getWorldName(world) + File.separator + uuid + File.separator + "index");
if (file.exists()) { if (file.exists()) {
@ -392,16 +452,19 @@ public class LocalSession {
while (iter.hasNext()) { while (iter.hasNext()) {
Object item = iter.next(); Object item = iter.next();
if (++i > cutoffIndex) { if (++i > cutoffIndex) {
FaweChangeSet changeSet;
if (item instanceof FaweChangeSet) { if (item instanceof FaweChangeSet) {
FaweChangeSet changeSet = (FaweChangeSet) item; changeSet = (FaweChangeSet) item;
historySize -= MainUtil.getSizeInMemory(changeSet); } else {
changeSet = getChangeSet(item);
} }
historySize -= MainUtil.getSize(changeSet);
iter.remove(); iter.remove();
} }
} }
} }
FaweChangeSet changeSet = (FaweChangeSet) editSession.getChangeSet(); FaweChangeSet changeSet = (FaweChangeSet) editSession.getChangeSet();
historySize += MainUtil.getSizeInMemory(changeSet); historySize += MainUtil.getSize(changeSet);
if (append) { if (append) {
history.add(changeSet); history.add(changeSet);
if (getHistoryNegativeIndex() != 0) { if (getHistoryNegativeIndex() != 0) {
@ -413,7 +476,8 @@ public class LocalSession {
} }
while ((history.size() > MAX_HISTORY_SIZE || (historySize >> 20) > limitMb) && history.size() > 1) { while ((history.size() > MAX_HISTORY_SIZE || (historySize >> 20) > limitMb) && history.size() > 1) {
FaweChangeSet item = (FaweChangeSet) history.remove(0); FaweChangeSet item = (FaweChangeSet) history.remove(0);
historySize -= MainUtil.getSizeInMemory(item); item.delete();
historySize -= MainUtil.getSize(item);
} }
} }
@ -648,7 +712,11 @@ public class LocalSession {
* @return the the world of the selection * @return the the world of the selection
*/ */
public World getSelectionWorld() { public World getSelectionWorld() {
return selector.getIncompleteRegion().getWorld(); World world = selector.getIncompleteRegion().getWorld();
if (world instanceof WorldWrapper) {
return ((WorldWrapper) world).getParent();
}
return world;
} }
/** /**

View File

@ -14,4 +14,4 @@ processResources {
} }
jar.destinationDir = file '../target' jar.destinationDir = file '../target'
jar.archiveName = "${parent.name}-${project.name}-${parent.version}.jar" jar.archiveName = "FastAsyncVoxelSniper-${project.name}-${parent.version}.jar"

View File

@ -45,6 +45,8 @@ public class Favs extends JavaPlugin {
} }
}); });
Fawe.debug("Injected VoxelSniper classes"); Fawe.debug("Injected VoxelSniper classes");
} catch (Throwable ignore) {} } catch (Throwable ignore) {
ignore.printStackTrace();
}
} }
} }