Serialize BlockStates

Banner, spawner and player heads can now be rolled back
This commit is contained in:
Brokkonaut
2018-07-31 02:19:40 +02:00
parent fca9c90032
commit 58e5cdf890
14 changed files with 539 additions and 99 deletions

View File

@ -4,7 +4,7 @@
<groupId>de.diddiz</groupId>
<artifactId>logblock</artifactId>
<version>1.13-SNAPSHOT</version>
<version>1.13.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>LogBlock</name>

View File

@ -1,5 +1,6 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.BukkitUtils;
import de.diddiz.util.Utils;
@ -14,29 +15,28 @@ import org.bukkit.inventory.ItemStack;
import java.sql.ResultSet;
import java.sql.SQLException;
import static de.diddiz.util.LoggingUtil.checkText;
import java.util.logging.Level;
public class BlockChange implements LookupCacheElement {
public final long id, date;
public final Location loc;
public final Actor actor;
public final String playerName;
// public final BlockData replaced, type;
public final int replacedMaterial, replacedData, typeMaterial, typeData;
public final String signtext;
public final byte[] replacedState, typeState;
public final ChestAccess ca;
public BlockChange(long date, Location loc, Actor actor, int replaced, int replacedData, int type, int typeData, String signtext, ChestAccess ca) {
public BlockChange(long date, Location loc, Actor actor, int replaced, int replacedData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) {
id = 0;
this.date = date;
this.loc = loc;
this.actor = actor;
this.replacedMaterial = replaced;
this.replacedData = replacedData;
this.replacedState = replacedState;
this.typeMaterial = type;
this.typeData = typeData;
this.signtext = checkText(signtext);
this.typeState = typeState;
this.ca = ca;
this.playerName = actor == null ? null : actor.getName();
}
@ -51,7 +51,8 @@ public class BlockChange implements LookupCacheElement {
replacedData = p.needType ? rs.getInt("replacedData") : -1;
typeMaterial = p.needType ? rs.getInt("type") : 0;
typeData = p.needType ? rs.getInt("typeData") : -1;
signtext = p.needSignText ? rs.getString("signtext") : null;
replacedState = p.needType ? rs.getBytes("replacedState") : null;
typeState = p.needType ? rs.getBytes("typeState") : null;
ChestAccess catemp = null;
if (p.needChestAccess) {
ItemStack stack = Utils.loadItemStack(rs.getBytes("item"));
@ -66,6 +67,32 @@ public class BlockChange implements LookupCacheElement {
public String toString() {
BlockData type = MaterialConverter.getBlockData(typeMaterial, typeData);
BlockData replaced = MaterialConverter.getBlockData(replacedMaterial, replacedData);
String typeDetails = null;
if (BlockStateCodecs.hasCodec(type.getMaterial())) {
try {
typeDetails = BlockStateCodecs.toString(type.getMaterial(), typeState);
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not parse BlockState for " + type.getMaterial(), e);
}
}
if (typeDetails == null) {
typeDetails = "";
} else {
typeDetails = " " + typeDetails;
}
String replacedDetails = null;
if (BlockStateCodecs.hasCodec(replaced.getMaterial())) {
try {
replacedDetails = BlockStateCodecs.toString(replaced.getMaterial(), replacedState);
} catch (Exception e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Could not parse BlockState for " + replaced.getMaterial(), e);
}
}
if (replacedDetails == null) {
replacedDetails = "";
} else {
replacedDetails = " " + replacedDetails;
}
final StringBuilder msg = new StringBuilder();
if (date > 0) {
msg.append(Config.formatter.format(date)).append(" ");
@ -73,14 +100,7 @@ public class BlockChange implements LookupCacheElement {
if (actor != null) {
msg.append(actor.getName()).append(" ");
}
if (signtext != null) {
final String action = BukkitUtils.isEmpty(type.getMaterial()) ? "destroyed " : "created ";
if (!signtext.contains("\0")) {
msg.append(action).append(signtext);
} else {
msg.append(action).append((!BukkitUtils.isEmpty(type.getMaterial()) ? type : replaced).getMaterial().name()).append(" [").append(signtext.replace("\0", "] [")).append("]");
}
} else if (type.equals(replaced)) {
if (type.equals(replaced)) {
if (BukkitUtils.isEmpty(type.getMaterial())) {
msg.append("did an unspecified action");
} else if (ca != null) {
@ -110,11 +130,11 @@ public class BlockChange implements LookupCacheElement {
msg.append("ran into ").append(type.getMaterial().name());
}
} else if (BukkitUtils.isEmpty(type.getMaterial())) {
msg.append("destroyed ").append(replaced.getMaterial().name());
msg.append("destroyed ").append(replaced.getMaterial().name()).append(replacedDetails);
} else if (BukkitUtils.isEmpty(replaced.getMaterial())) {
msg.append("created ").append(type.getMaterial().name());
msg.append("created ").append(type.getMaterial().name()).append(typeDetails);
} else {
msg.append("replaced ").append(replaced.getMaterial().name()).append(" with ").append(type.getMaterial().name());
msg.append("replaced ").append(replaced.getMaterial().name()).append(replacedDetails).append(" with ").append(type.getMaterial().name()).append(typeDetails);
}
if (loc != null) {
msg.append(" at ").append(loc.getBlockX()).append(":").append(loc.getBlockY()).append(":").append(loc.getBlockZ());

View File

@ -10,7 +10,6 @@ import de.diddiz.util.Utils;
import org.bukkit.ChatColor;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
@ -456,9 +455,6 @@ public class CommandsHandler implements CommandExecutor {
params.needType = true;
params.needData = true;
params.needPlayer = true;
if (params.types.isEmpty() || params.types.contains(Material.SIGN) || params.types.contains(Material.WALL_SIGN)) {
params.needSignText = true;
}
if (params.bct == BlockChangeType.CHESTACCESS || params.bct == BlockChangeType.ALL) {
params.needChestAccess = true;
}
@ -521,9 +517,6 @@ public class CommandsHandler implements CommandExecutor {
params.needType = true;
params.needData = true;
params.needPlayer = true;
if (params.types.isEmpty() || params.types.contains(Material.SIGN) || params.types.contains(Material.WALL_SIGN)) {
params.needSignText = true;
}
if (params.bct == BlockChangeType.CHESTACCESS || params.bct == BlockChangeType.ALL) {
params.needChestAccess = true;
}
@ -646,7 +639,6 @@ public class CommandsHandler implements CommandExecutor {
params.needCoords = true;
params.needType = true;
params.needData = true;
params.needSignText = true;
params.needChestAccess = true;
params.order = Order.DESC;
params.sum = SummarizationMode.NONE;
@ -674,7 +666,7 @@ public class CommandsHandler implements CommandExecutor {
if (stack != null) {
chestaccess = new ChestAccess(stack, rs.getBoolean("itemremove"));
}
editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getInt("type"), rs.getInt("typeData"), rs.getString("signtext"), chestaccess);
editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), chestaccess);
}
final int changes = editor.getSize();
if (changes > 10000) {
@ -725,7 +717,6 @@ public class CommandsHandler implements CommandExecutor {
params.needCoords = true;
params.needType = true;
params.needData = true;
params.needSignText = true;
params.needChestAccess = true;
params.order = Order.ASC;
params.sum = SummarizationMode.NONE;
@ -749,7 +740,7 @@ public class CommandsHandler implements CommandExecutor {
if (stack != null) {
chestaccess = new ChestAccess(stack, !rs.getBoolean("itemremove"));
}
editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("type"), rs.getInt("typeData"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getString("signtext"), chestaccess);
editor.queueEdit(rs.getInt("x"), rs.getInt("y"), rs.getInt("z"), rs.getInt("type"), rs.getInt("typeData"), rs.getBytes("typeState"), rs.getInt("replaced"), rs.getInt("replacedData"), rs.getBytes("replacedState"), chestaccess);
}
final int changes = editor.getSize();
if (!params.silent) {
@ -824,23 +815,23 @@ public class CommandsHandler implements CommandExecutor {
state.execute("DELETE `" + table + "` FROM `" + table + "-blocks` " + join + params.getWhere());
sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + ". Deleted " + deleted + " entries.");
}
rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL");
rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL");
rs.next();
if ((deleted = rs.getInt(1)) > 0) {
if (dumpDeletedLog) {
state.execute("SELECT id, signtext FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-sign " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'");
state.execute("SELECT id, replacedState, typeState FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-state " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'");
}
state.execute("DELETE `" + table + "-sign` FROM `" + table + "-sign` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;");
sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-sign. Deleted " + deleted + " entries.");
state.execute("DELETE `" + table + "-state` FROM `" + table + "-state` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;");
sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-state. Deleted " + deleted + " entries.");
}
rs = state.executeQuery("SELECT COUNT(*) FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL");
rs.next();
if ((deleted = rs.getInt(1)) > 0) {
if (dumpDeletedLog) {
state.execute("SELECT id, item, itemremove FROM `" + table + "-chest` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-chest " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'");
state.execute("SELECT id, item, itemremove FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL INTO OUTFILE '" + new File(dumpFolder, time + " " + table + "-chest " + params.getTitle() + ".csv").getAbsolutePath().replace("\\", "\\\\") + "' FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '\"' LINES TERMINATED BY '\n'");
}
state.execute("DELETE `" + table + "-chest` FROM `" + table + "-chest` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;");
sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-chest. Deleted " + deleted + " entries.");
state.execute("DELETE `" + table + "-chestdata` FROM `" + table + "-chestdata` LEFT JOIN `" + table + "-blocks` USING (id) WHERE `" + table + "-blocks`.id IS NULL;");
sender.sendMessage(ChatColor.GREEN + "Cleared out table " + table + "-chestdata. Deleted " + deleted + " entries.");
}
} catch (final Exception ex) {
sender.sendMessage(ChatColor.RED + "Exception, check error log");

View File

@ -1,5 +1,7 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.blockstate.BlockStateCodecSign;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.events.BlockChangePreLogEvent;
import de.diddiz.util.Utils;
@ -63,7 +65,7 @@ public class Consumer extends TimerTask {
* Data of the block after the change
*/
public void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter) {
queueBlock(actor, loc, typeBefore, typeAfter, null, null);
queueBlock(actor, loc, typeBefore, typeAfter, null, null, null);
}
/**
@ -75,7 +77,7 @@ public class Consumer extends TimerTask {
* Blockstate of the block before actually being destroyed.
*/
public void queueBlockBreak(Actor actor, BlockState before) {
queueBlockBreak(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData());
queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), null, BlockStateCodecs.serialize(before), null, null);
}
/**
@ -103,7 +105,7 @@ public class Consumer extends TimerTask {
* Blockstate of the block after actually being placed.
*/
public void queueBlockPlace(Actor actor, BlockState after) {
queueBlockPlace(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), after.getBlockData());
queueBlock(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), null, after.getBlockData(), null, BlockStateCodecs.serialize(after), null);
}
/**
@ -133,7 +135,7 @@ public class Consumer extends TimerTask {
* Blockstate of the block after actually being placed.
*/
public void queueBlockReplace(Actor actor, BlockState before, BlockState after) {
queueBlockReplace(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), after.getBlockData());
queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), after.getBlockData(), BlockStateCodecs.serialize(before), BlockStateCodecs.serialize(after), null);
}
/**
@ -149,7 +151,7 @@ public class Consumer extends TimerTask {
* Data of the block after being replaced
*/
public void queueBlockReplace(Actor actor, BlockState before, BlockData typeAfter) {
queueBlockReplace(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), typeAfter);
queueBlock(actor, new Location(before.getWorld(), before.getX(), before.getY(), before.getZ()), before.getBlockData(), typeAfter, BlockStateCodecs.serialize(before), null, null);
}
/**
@ -165,12 +167,11 @@ public class Consumer extends TimerTask {
* Blockstate of the block after actually being placed.
*/
public void queueBlockReplace(Actor actor, BlockData typeBefore, BlockState after) {
queueBlockReplace(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), typeBefore, after.getBlockData());
queueBlock(actor, new Location(after.getWorld(), after.getX(), after.getY(), after.getZ()), typeBefore, after.getBlockData(), null, BlockStateCodecs.serialize(after), null);
}
public void queueBlockReplace(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter) {
queueBlockBreak(actor, loc, typeBefore);
queueBlockPlace(actor, loc, typeAfter);
queueBlock(actor, loc, typeBefore, typeAfter, null, null, null);
}
/**
@ -211,7 +212,7 @@ public class Consumer extends TimerTask {
* Data of the item taken/stored
*/
public void queueChestAccess(Actor actor, Location loc, BlockData type, ItemStack itemStack, boolean remove) {
queueBlock(actor, loc, type, type, null, new ChestAccess(itemStack, remove));
queueBlock(actor, loc, type, type, null, null, new ChestAccess(itemStack, remove));
}
/**
@ -326,11 +327,8 @@ public class Consumer extends TimerTask {
* @param lines
* The four lines on the sign.
*/
public void queueSignBreak(Actor actor, Location loc, BlockData type, String[] lines) {
if ((type.getMaterial() != Material.SIGN && type.getMaterial() != Material.WALL_SIGN) || lines == null || lines.length != 4) {
return;
}
queueBlock(actor, loc, type, null, lines[0] + "\0" + lines[1] + "\0" + lines[2] + "\0" + lines[3], null);
public void queueSignBreak(Actor actor, Location loc, BlockData type, byte[] typeState) {
queueBlock(actor, loc, type, null, typeState, null, null);
}
/**
@ -342,7 +340,7 @@ public class Consumer extends TimerTask {
* The sign being broken
*/
public void queueSignBreak(Actor actor, Sign sign) {
queueSignBreak(actor, new Location(sign.getWorld(), sign.getX(), sign.getY(), sign.getZ()), sign.getBlockData(), sign.getLines());
queueSignBreak(actor, new Location(sign.getWorld(), sign.getX(), sign.getY(), sign.getZ()), sign.getBlockData(), BlockStateCodecs.serialize(sign));
}
/**
@ -363,7 +361,7 @@ public class Consumer extends TimerTask {
if ((type.getMaterial() != Material.SIGN && type.getMaterial() != Material.WALL_SIGN) || lines == null || lines.length != 4) {
return;
}
queueBlock(actor, loc, null, type, lines[0] + "\0" + lines[1] + "\0" + lines[2] + "\0" + lines[3], null);
queueBlock(actor, loc, null, type, null, Utils.serializeYamlConfiguration(BlockStateCodecSign.serialize(lines)), null);
}
/**
@ -585,7 +583,7 @@ public class Consumer extends TimerTask {
return playerIds.containsKey(actor);
}
private void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter, String signtext, ChestAccess ca) {
private void queueBlock(Actor actor, Location loc, BlockData typeBefore, BlockData typeAfter, byte[] stateBefore, byte[] stateAfter, ChestAccess ca) {
if (typeBefore == null) {
typeBefore = Bukkit.createBlockData(Material.AIR);
}
@ -594,7 +592,7 @@ public class Consumer extends TimerTask {
}
if (Config.fireCustomEvents) {
// Create and call the event
BlockChangePreLogEvent event = new BlockChangePreLogEvent(actor, loc, typeBefore, typeAfter, signtext, ca);
BlockChangePreLogEvent event = new BlockChangePreLogEvent(actor, loc, typeBefore, typeAfter, null, ca);
logblock.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
@ -605,7 +603,7 @@ public class Consumer extends TimerTask {
loc = event.getLocation();
typeBefore = event.getTypeBefore();
typeAfter = event.getTypeAfter();
signtext = event.getSignText();
// signtext = event.getSignText();
ca = event.getChestAccess();
}
// Do this last so LogBlock still has final say in what is being added
@ -620,7 +618,7 @@ public class Consumer extends TimerTask {
int typeMaterialId = MaterialConverter.getOrAddMaterialId(typeString);
int typeStateId = MaterialConverter.getOrAddBlockStateId(typeString);
queue.add(new BlockRow(loc, actor, replacedMaterialId, replacedStateId, typeMaterialId, typeStateId, signtext, ca));
queue.add(new BlockRow(loc, actor, replacedMaterialId, replacedStateId, stateBefore, typeMaterialId, typeStateId, stateAfter, ca));
}
private String playerID(Actor actor) {
@ -670,19 +668,19 @@ public class Consumer extends TimerTask {
private class BlockRow extends BlockChange implements MergeableRow {
private Connection connection;
public BlockRow(Location loc, Actor actor, int replaced, int replacedData, int type, int typeData, String signtext, ChestAccess ca) {
super(System.currentTimeMillis() / 1000, loc, actor, replaced, replacedData, type, typeData, signtext, ca);
public BlockRow(Location loc, Actor actor, int replaced, int replacedData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) {
super(System.currentTimeMillis() / 1000, loc, actor, replaced, replacedData, replacedState, type, typeData, typeState, ca);
}
@Override
public String[] getInserts() {
final String table = getWorldConfig(loc.getWorld()).table;
final String[] inserts = new String[ca != null || signtext != null ? 2 : 1];
final String[] inserts = new String[ca != null || replacedState != null || typeState != null ? 2 : 1];
inserts[0] = "INSERT INTO `" + table + "-blocks` (date, playerid, replaced, replaceddata, type, typedata, x, y, z) VALUES (FROM_UNIXTIME(" + date + "), " + playerID(actor) + ", " + replacedMaterial + ", " + replacedData + ", " + typeMaterial + ", " + typeData + ", '" + loc.getBlockX()
+ "', " + safeY(loc) + ", '" + loc.getBlockZ() + "');";
if (signtext != null) {
inserts[1] = "INSERT INTO `" + table + "-sign` (id, signtext) values (LAST_INSERT_ID(), '" + mysqlTextEscape(signtext) + "');";
if (replacedState != null || typeState != null) {
inserts[1] = "INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES('" + Utils.mysqlEscapeBytes(replacedState) + "', '" + Utils.mysqlEscapeBytes(typeState) + "', LAST_INSERT_ID());";
} else if (ca != null) {
inserts[1] = "INSERT INTO `" + table + "-chestdata` (id, item, itemremoved) values (LAST_INSERT_ID(), '" + Utils.mysqlEscapeBytes(Utils.saveItemStack(ca.itemStack)) + "', " + (ca.remove ? 1 : 0) + ");";
}
@ -727,10 +725,11 @@ public class Consumer extends TimerTask {
rs.next();
id = rs.getInt(1);
if (signtext != null) {
ps = connection.prepareStatement("INSERT INTO `" + table + "-sign` (signtext, id) VALUES(?, ?)");
ps.setString(1, signtext);
ps.setInt(2, id);
if (typeState != null || replacedState != null) {
ps = connection.prepareStatement("INSERT INTO `" + table + "-state` (replacedState, typeState, id) VALUES(?, ?, ?)");
ps.setBytes(1, replacedState);
ps.setBytes(2, typeState);
ps.setInt(3, id);
ps.executeUpdate();
} else if (ca != null) {
ps = connection.prepareStatement("INSERT INTO `" + table + "-chestdata` (item, itemremove, id) values (?, ?, ?)");
@ -772,7 +771,7 @@ public class Consumer extends TimerTask {
@Override
public boolean isUnique() {
return !(signtext == null && ca == null && playerIds.containsKey(actor));
return !(typeState == null && replacedState == null && ca == null && playerIds.containsKey(actor));
}
@Override

View File

@ -34,7 +34,7 @@ public final class QueryParams implements Cloneable {
public List<Integer> typeIds = new ArrayList<Integer>();
public World world = null;
public String match = null;
public boolean needCount = false, needId = false, needDate = false, needType = false, needData = false, needPlayer = false, needCoords = false, needSignText = false, needChestAccess = false, needMessage = false, needKiller = false, needVictim = false, needWeapon = false;
public boolean needCount = false, needId = false, needDate = false, needType = false, needData = false, needPlayer = false, needCoords = false, needChestAccess = false, needMessage = false, needKiller = false, needVictim = false, needWeapon = false;
private final LogBlock logblock;
public QueryParams(LogBlock logblock) {
@ -145,8 +145,8 @@ public final class QueryParams implements Cloneable {
if (needCoords) {
select += "x, y, z, ";
}
if (needSignText) {
select += "signtext, ";
if (needData) {
select += "replacedState, typeState, ";
}
if (needChestAccess) {
select += "item, itemremove, ";
@ -157,8 +157,8 @@ public final class QueryParams implements Cloneable {
if (needPlayer || players.size() > 0) {
from += "INNER JOIN `lb-players` USING (playerid) ";
}
if (needSignText) {
from += "LEFT JOIN `" + getTable() + "-sign` USING (id) ";
if (!needCount && needData) {
from += "LEFT JOIN `" + getTable() + "-state` USING (id) ";
}
if (needChestAccess)
// If BlockChangeType is CHESTACCESS, we can use more efficient query

View File

@ -1,5 +1,6 @@
package de.diddiz.LogBlock;
import de.diddiz.LogBlock.blockstate.BlockStateCodecSign;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.LogBlock.config.WorldConfig;
import de.diddiz.util.UUIDFetcher;
@ -386,7 +387,7 @@ class Updater {
}
for (final WorldConfig wcfg : getLoggedWorlds()) {
if (wcfg.isLogging(Logging.SIGNTEXT)) {
checkCharset(wcfg.table + "-sign","signtext",st);
// checkCharset(wcfg.table + "-sign","signtext",st);
}
}
st.close();
@ -565,7 +566,7 @@ class Updater {
}
rs.close();
PreparedStatement updateWeaponStatement = conn.prepareStatement("UPDATE `" + wcfg.table + "`-kills SET weapon = ? WHERE id = ?");
PreparedStatement updateWeaponStatement = conn.prepareStatement("UPDATE `" + wcfg.table + "-kills` SET weapon = ? WHERE id = ?");
for (int start = 0;; start += 10000) {
rs = st.executeQuery("SELECT id, weapon FROM `" + wcfg.table + "-kills` ORDER BY id ASC LIMIT " + start + ",10000");
boolean anyUpdate = false;
@ -622,7 +623,85 @@ class Updater {
}
config.set("version", "1.13.0");
}
if (configVersion.compareTo(new ComparableVersion("1.13.1")) < 0) {
getLogger().info("Updating tables to 1.13.1 ...");
try {
final Connection conn = logblock.getConnection();
conn.setAutoCommit(false);
final Statement st = conn.createStatement();
for (final WorldConfig wcfg : getLoggedWorlds()) {
getLogger().info("Processing world " + wcfg.world + "...");
if (wcfg.isLogging(Logging.SIGNTEXT)) {
int rowsToConvert = 0;
int done = 0;
try {
ResultSet rs = st.executeQuery("SELECT count(*) as rowcount FROM `" + wcfg.table + "-sign`");
if (rs.next()) {
rowsToConvert = rs.getInt(1);
getLogger().info("Converting " + rowsToConvert + " entries in " + wcfg.table + "-sign");
}
rs.close();
PreparedStatement insertSignState = conn.prepareStatement("INSERT INTO `" + wcfg.table + "-state` (id, replacedState, typeState) VALUES (?, ?, ?)");
PreparedStatement deleteSign = conn.prepareStatement("DELETE FROM `" + wcfg.table + "-sign` WHERE id = ?");
while (true) {
rs = st.executeQuery("SELECT id, signtext, replaced, type FROM `" + wcfg.table + "-sign` LEFT JOIN `" + wcfg.table + "-blocks` USING (id) ORDER BY id ASC LIMIT 10000");
boolean anyRow = false;
while (rs.next()) {
anyRow = true;
int id = rs.getInt("id");
String signText = rs.getString("signtext");
int replaced = rs.getInt("replaced");
boolean nullBlock = rs.wasNull();
int type = rs.getInt("type");
if (!nullBlock && signText != null) {
String[] lines = signText.split("\0", 4);
byte[] bytes = Utils.serializeYamlConfiguration(BlockStateCodecSign.serialize(lines));
Material replacedMaterial = MaterialConverter.getBlockData(replaced, -1).getMaterial();
Material typeMaterial = MaterialConverter.getBlockData(type, -1).getMaterial();
boolean wasSign = replacedMaterial == Material.SIGN || replacedMaterial == Material.WALL_SIGN;
boolean isSign = typeMaterial == Material.SIGN || typeMaterial == Material.WALL_SIGN;
insertSignState.setInt(1, id);
insertSignState.setBytes(2, wasSign ? bytes : null);
insertSignState.setBytes(3, isSign ? bytes : null);
insertSignState.addBatch();
}
deleteSign.setInt(1, id);
deleteSign.addBatch();
done++;
}
rs.close();
if (!anyRow) {
break;
}
insertSignState.executeBatch();
deleteSign.executeBatch();
conn.commit();
getLogger().info("Done: " + done + "/" + rowsToConvert + " (" + (rowsToConvert > 0 ? (done * 100 / rowsToConvert) : 100) + "%)");
}
insertSignState.close();
deleteSign.close();
} catch (SQLException e) {
getLogger().info("Could not convert " + wcfg.table + "-sign: " + e.getMessage());
}
}
}
st.close();
conn.close();
} catch (final SQLException ex) {
Bukkit.getLogger().log(Level.SEVERE, "[Updater] Error: ", ex);
return false;
}
config.set("version", "1.13.1");
}
logblock.saveConfig();
return true;
}
@ -667,8 +746,9 @@ class Updater {
for (final WorldConfig wcfg : getLoggedWorlds()) {
createTable(dbm, state, wcfg.table + "-blocks", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, playerid INT UNSIGNED NOT NULL, replaced SMALLINT UNSIGNED NOT NULL, replacedData SMALLINT NOT NULL, type SMALLINT UNSIGNED NOT NULL, typeData SMALLINT NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT UNSIGNED NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id), KEY coords (x, z, y), KEY date (date), KEY playerid (playerid))");
createTable(dbm, state, wcfg.table + "-sign", "(id INT UNSIGNED NOT NULL, signtext VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
// createTable(dbm, state, wcfg.table + "-sign", "(id INT UNSIGNED NOT NULL, signtext VARCHAR(255) NOT NULL, PRIMARY KEY (id)) DEFAULT CHARSET " + charset);
createTable(dbm, state, wcfg.table + "-chestdata", "(id INT UNSIGNED NOT NULL, item MEDIUMBLOB, itemremove TINYINT, PRIMARY KEY (id))");
createTable(dbm, state, wcfg.table + "-state", "(id INT UNSIGNED NOT NULL, replacedState MEDIUMBLOB NULL, typeState MEDIUMBLOB NULL, PRIMARY KEY (id))");
if (wcfg.isLogging(Logging.KILL)) {
createTable(dbm, state, wcfg.table + "-kills", "(id INT UNSIGNED NOT NULL AUTO_INCREMENT, date DATETIME NOT NULL, killer INT UNSIGNED, victim INT UNSIGNED NOT NULL, weapon SMALLINT UNSIGNED NOT NULL, x MEDIUMINT NOT NULL, y SMALLINT NOT NULL, z MEDIUMINT NOT NULL, PRIMARY KEY (id))");
}

View File

@ -7,7 +7,6 @@ import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.Bisected.Half;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.type.Bed;
@ -20,6 +19,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.util.BukkitUtils;
import java.io.File;
@ -76,8 +76,8 @@ public class WorldEditor implements Runnable {
this.sender = sender;
}
public void queueEdit(int x, int y, int z, int replaced, int replaceData, int type, int typeData, String signtext, ChestAccess item) {
edits.add(new Edit(0, new Location(world, x, y, z), null, replaced, replaceData, type, typeData, signtext, item));
public void queueEdit(int x, int y, int z, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess item) {
edits.add(new Edit(0, new Location(world, x, y, z), null, replaced, replaceData, replacedState, type, typeData, typeState, item));
}
public long getElapsedTime() {
@ -153,8 +153,8 @@ public class WorldEditor implements Runnable {
}
private class Edit extends BlockChange {
public Edit(long time, Location loc, Actor actor, int replaced, int replaceData, int type, int typeData, String signtext, ChestAccess ca) {
super(time, loc, actor, replaced, replaceData, type, typeData, signtext, ca);
public Edit(long time, Location loc, Actor actor, int replaced, int replaceData, byte[] replacedState, int type, int typeData, byte[] typeState, ChestAccess ca) {
super(time, loc, actor, replaced, replaceData,replacedState , type, typeData, typeState, ca);
}
PerformResult perform() throws WorldEditorException {
@ -172,7 +172,7 @@ public class WorldEditor implements Runnable {
if (BukkitUtils.isEmpty(replacedBlock.getMaterial()) && BukkitUtils.isEmpty(block.getType())) {
return PerformResult.NO_ACTION;
}
final BlockState state = block.getState();
BlockState state = block.getState();
if (!world.isChunkLoaded(block.getChunk())) {
world.loadChunk(block.getChunk());
}
@ -217,21 +217,18 @@ public class WorldEditor implements Runnable {
}
block.setBlockData(replacedBlock);
BlockData newData = block.getBlockData();
if (BlockStateCodecs.hasCodec(replacedBlock.getMaterial())) {
state = block.getState();
try {
BlockStateCodecs.deserialize(state, replacedState);
state.update();
} catch (Exception e) {
throw new WorldEditorException("Failed to restore blockstate of " + block.getType() + ": " + e, block.getLocation());
}
}
final Material curtype = block.getType();
if (signtext != null && (curtype == Material.SIGN || curtype == Material.WALL_SIGN)) {
final Sign sign = (Sign) block.getState();
final String[] lines = signtext.split("\0", 4);
if (lines.length < 4) {
return PerformResult.NO_ACTION;
}
for (int i = 0; i < 4; i++) {
sign.setLine(i, lines[i]);
}
if (!sign.update()) {
throw new WorldEditorException("Failed to update signtext of " + block.getType(), block.getLocation());
}
} else if (newData instanceof Bed) {
if (newData instanceof Bed) {
final Bed bed = (Bed) newData;
final Block secBlock = bed.getPart() == Part.HEAD ? block.getRelative(bed.getFacing().getOppositeFace()) : block.getRelative(bed.getFacing());
if (secBlock.isEmpty()) {

View File

@ -0,0 +1,15 @@
package de.diddiz.LogBlock.blockstate;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.configuration.file.YamlConfiguration;
public interface BlockStateCodec {
Material[] getApplicableMaterials();
YamlConfiguration serialize(BlockState state);
void deserialize(BlockState state, YamlConfiguration conf);
String toString(YamlConfiguration conf);
}

View File

@ -0,0 +1,70 @@
package de.diddiz.LogBlock.blockstate;
import java.util.List;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.block.Banner;
import org.bukkit.block.BlockState;
import org.bukkit.block.banner.Pattern;
import org.bukkit.block.banner.PatternType;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecBanner implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.WHITE_BANNER, Material.ORANGE_BANNER, Material.MAGENTA_BANNER, Material.LIGHT_BLUE_BANNER, Material.YELLOW_BANNER, Material.LIME_BANNER, Material.PINK_BANNER, Material.GRAY_BANNER, Material.LIGHT_GRAY_BANNER, Material.CYAN_BANNER, Material.PURPLE_BANNER,
Material.BLUE_BANNER, Material.BROWN_BANNER, Material.GREEN_BANNER, Material.RED_BANNER, Material.BLACK_BANNER, Material.WHITE_WALL_BANNER, Material.ORANGE_WALL_BANNER, Material.MAGENTA_WALL_BANNER, Material.LIGHT_BLUE_WALL_BANNER, Material.YELLOW_WALL_BANNER,
Material.LIME_WALL_BANNER, Material.PINK_WALL_BANNER, Material.GRAY_WALL_BANNER, Material.LIGHT_GRAY_WALL_BANNER, Material.CYAN_WALL_BANNER, Material.PURPLE_WALL_BANNER, Material.BLUE_WALL_BANNER, Material.BROWN_WALL_BANNER, Material.GREEN_WALL_BANNER, Material.RED_WALL_BANNER,
Material.BLACK_WALL_BANNER };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Banner) {
Banner banner = (Banner) state;
int nr = 0;
List<Pattern> patterns = banner.getPatterns();
if (!patterns.isEmpty()) {
YamlConfiguration conf = new YamlConfiguration();
ConfigurationSection patternsSection = conf.createSection("patterns");
for (Pattern pattern : patterns) {
ConfigurationSection section = patternsSection.createSection(Integer.toString(nr));
section.set("color", pattern.getColor().name());
section.set("pattern", pattern.getPattern().name());
nr++;
}
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Banner) {
Banner banner = (Banner) state;
int oldPatterns = banner.getPatterns().size();
for (int i = 0; i < oldPatterns; i++) {
banner.removePattern(0);
}
ConfigurationSection patternsSection = conf == null ? null : conf.getConfigurationSection("patterns");
if (patternsSection != null) {
for (String key : patternsSection.getKeys(false)) {
ConfigurationSection section = patternsSection.getConfigurationSection(key);
if (section != null) {
DyeColor color = DyeColor.valueOf(section.getString("color"));
PatternType type = PatternType.valueOf(section.getString("pattern"));
banner.addPattern(new Pattern(color, type));
}
}
}
}
}
@Override
public String toString(YamlConfiguration conf) {
return null;
}
}

View File

@ -0,0 +1,76 @@
package de.diddiz.LogBlock.blockstate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecSign implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.WALL_SIGN, Material.SIGN };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Sign) {
Sign sign = (Sign) state;
String[] lines = sign.getLines();
boolean hasText = false;
for (int i = 0; i < lines.length; i++) {
if (lines[i] != null && lines[i].length() > 0) {
hasText = true;
break;
}
}
if (hasText) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("lines", Arrays.asList(lines));
return conf;
}
}
return null;
}
/**
* This is required for the SignChangeEvent, because we have no BlockState there.
*/
public static YamlConfiguration serialize(String[] lines) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("lines", Arrays.asList(lines));
return conf;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Sign) {
Sign sign = (Sign) state;
List<String> lines = Collections.emptyList();
if (conf != null) {
lines = conf.getStringList("lines");
}
for (int i = 0; i < 4; i++) {
String line = lines.size() > i && lines.get(i) != null ? lines.get(i) : "";
sign.setLine(i, line);
}
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
StringBuilder sb = new StringBuilder();
for (String line : conf.getStringList("lines")) {
if (sb.length() > 0)
sb.append(" ");
sb.append("[").append(line).append("]");
}
return sb.toString();
}
return null;
}
}

View File

@ -0,0 +1,54 @@
package de.diddiz.LogBlock.blockstate;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.block.BlockState;
import org.bukkit.block.Skull;
import org.bukkit.configuration.file.YamlConfiguration;
public class BlockStateCodecSkull implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.PLAYER_WALL_HEAD, Material.PLAYER_HEAD };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof Skull) {
Skull skull = (Skull) state;
OfflinePlayer owner = skull.hasOwner() ? skull.getOwningPlayer() : null;
if (owner != null) {
YamlConfiguration conf = new YamlConfiguration();
conf.set("owner", owner.getUniqueId().toString());
return conf;
}
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof Skull) {
Skull skull = (Skull) state;
UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner"));
if (ownerId == null) {
skull.setOwningPlayer(null);
} else {
skull.setOwningPlayer(Bukkit.getOfflinePlayer(ownerId));
}
}
}
@Override
public String toString(YamlConfiguration conf) {
UUID ownerId = conf == null ? null : UUID.fromString(conf.getString("owner"));
if (ownerId != null) {
OfflinePlayer owner = Bukkit.getOfflinePlayer(ownerId);
return "[" + (owner.getName() != null ? owner.getName() : owner.getUniqueId().toString()) + "]";
}
return null;
}
}

View File

@ -0,0 +1,60 @@
package de.diddiz.LogBlock.blockstate;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.block.CreatureSpawner;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.EntityType;
public class BlockStateCodecSpawner implements BlockStateCodec {
@Override
public Material[] getApplicableMaterials() {
return new Material[] { Material.SPAWNER };
}
@Override
public YamlConfiguration serialize(BlockState state) {
if (state instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner) state;
YamlConfiguration conf = new YamlConfiguration();
conf.set("delay", spawner.getDelay());
conf.set("maxNearbyEntities", spawner.getMaxNearbyEntities());
conf.set("maxSpawnDelay", spawner.getMaxSpawnDelay());
conf.set("minSpawnDelay", spawner.getMinSpawnDelay());
conf.set("requiredPlayerRange", spawner.getRequiredPlayerRange());
conf.set("spawnCount", spawner.getSpawnCount());
conf.set("spawnedType", spawner.getSpawnedType().name());
conf.set("spawnRange", spawner.getSpawnRange());
return conf;
}
return null;
}
@Override
public void deserialize(BlockState state, YamlConfiguration conf) {
if (state instanceof CreatureSpawner) {
CreatureSpawner spawner = (CreatureSpawner) state;
if (conf != null) {
spawner.setDelay(conf.getInt("delay"));
spawner.setMaxNearbyEntities(conf.getInt("maxNearbyEntities"));
spawner.setMaxSpawnDelay(conf.getInt("maxSpawnDelay"));
spawner.setMinSpawnDelay(conf.getInt("minSpawnDelay"));
spawner.setRequiredPlayerRange(conf.getInt("requiredPlayerRange"));
spawner.setSpawnCount(conf.getInt("spawnCount"));
spawner.setSpawnedType(EntityType.valueOf(conf.getString("spawnedType")));
spawner.setSpawnRange(conf.getInt("spawnRange"));
}
}
}
@Override
public String toString(YamlConfiguration conf) {
if (conf != null) {
EntityType entity = EntityType.valueOf(conf.getString("spawnedType"));
if (entity != null) {
return "[" + entity + "]";
}
}
return null;
}
}

View File

@ -0,0 +1,80 @@
package de.diddiz.LogBlock.blockstate;
import java.util.EnumMap;
import java.util.Map;
import java.util.logging.Level;
import org.bukkit.Material;
import org.bukkit.block.BlockState;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConfiguration;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.util.Utils;
public class BlockStateCodecs {
private static Map<Material, BlockStateCodec> codecs = new EnumMap<>(Material.class);
public static void registerCodec(BlockStateCodec codec) {
Material[] materials = codec.getApplicableMaterials();
for (Material material : materials) {
if (codecs.containsKey(material)) {
throw new IllegalArgumentException("BlockStateCodec for " + material + " already registered!");
}
codecs.put(material, codec);
}
}
static {
registerCodec(new BlockStateCodecSign());
registerCodec(new BlockStateCodecSkull());
registerCodec(new BlockStateCodecBanner());
registerCodec(new BlockStateCodecSpawner());
}
public static boolean hasCodec(Material material) {
return codecs.containsKey(material);
}
public static byte[] serialize(BlockState state) {
BlockStateCodec codec = codecs.get(state.getType());
if (codec != null) {
YamlConfiguration serialized = codec.serialize(state);
if (serialized != null && !serialized.getKeys(false).isEmpty()) {
return Utils.serializeYamlConfiguration(serialized);
}
}
return null;
}
public static void deserialize(BlockState block, byte[] state) {
BlockStateCodec codec = codecs.get(block.getType());
if (codec != null) {
YamlConfiguration conf = null;
try {
if (state != null) {
conf = Utils.deserializeYamlConfiguration(state);
}
} catch (InvalidConfigurationException e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while deserializing BlockState", e);
}
codec.deserialize(block, conf);
}
}
public static String toString(Material material, byte[] state) {
BlockStateCodec codec = codecs.get(material);
if (codec != null) {
YamlConfiguration conf = null;
try {
if (state != null) {
conf = Utils.deserializeYamlConfiguration(state);
}
} catch (InvalidConfigurationException e) {
LogBlock.getInstance().getLogger().log(Level.SEVERE, "Exception while deserializing BlockState", e);
}
return codec.toString(conf);
}
return null;
}
}

View File

@ -14,6 +14,7 @@ import com.sk89q.worldedit.world.block.BlockStateHolder;
import de.diddiz.LogBlock.LogBlock;
import de.diddiz.LogBlock.Logging;
import de.diddiz.LogBlock.blockstate.BlockStateCodecs;
import de.diddiz.LogBlock.config.Config;
import de.diddiz.util.BukkitUtils;
@ -22,8 +23,6 @@ import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.BlockData;
import java.util.logging.Level;
@ -104,9 +103,8 @@ public class WorldEditLoggingHook {
Material typeBefore = origin.getType();
// Check to see if we've broken a sign
if (Config.isLogging(location.getWorld().getName(), Logging.SIGNTEXT) && (typeBefore == Material.SIGN || typeBefore == Material.WALL_SIGN)) {
BlockState stateBefore = origin.getState();
plugin.getConsumer().queueSignBreak(lbActor, (Sign) stateBefore);
if (BlockStateCodecs.hasCodec(typeBefore)) {
plugin.getConsumer().queueBlockBreak(lbActor, origin.getState());
} else if (!origin.isEmpty()) {
plugin.getConsumer().queueBlockBreak(lbActor, location, origin.getBlockData());
}