Store warzone structures in main zone database

Instead of storing strutures in /dat/warzone-x/volume-y.sl3 anymore, they are stored in the main database file created for the warzone in /dat/warzone-x/volume-x.sl3. Tables is created for each structure, with the prefix being structure_HASH_. The HASH is a string hashCode run through a bitwise AND with Integer.MAX_VALUE, to prevent negative values from showing.

This is a step on the road to storing everything in a single database for the warzone. My plan is to eventually store warzone configuration in the database as well. In the ideal setup, there would be a table for teams, structure definitions, loadouts, and materials. Sadly, for configuration, we would most likely have to store teamcfg & zonecfg rules in a key-value table, unless @taoneill or @grinning has another idea.

I still need to do testing to make sure everything is backward-compatible. This conversion happens properly for nimitz-format zones and zones in the process of converting from degaulle format. When messing around earlier, I found that .txt configurations were broken due to numbered teams. I need to take a look at that. Most likely it is due to WarzoneTxtMapper not being maintained. The loader will probably have to be modified to load the new volumes and teams properly.
This commit is contained in:
cmastudios 2013-12-08 01:46:26 -06:00
parent 034b51ae03
commit 84a5750063
6 changed files with 161 additions and 29 deletions

View File

@ -1,5 +1,9 @@
package com.tommytony.war;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
@ -10,6 +14,8 @@ import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import com.tommytony.war.mapper.VolumeMapper;
import com.tommytony.war.mapper.ZoneVolumeMapper;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
@ -1835,4 +1841,37 @@ public class Warzone {
}
return this.getTeleport();
}
public Volume loadStructure(String volName, World world) throws SQLException {
return loadStructure(volName, world, ZoneVolumeMapper.getZoneConnection(volume, name, world));
}
public Volume loadStructure(String volName, Connection zoneConnection) throws SQLException {
return loadStructure(volName, world, zoneConnection);
}
public Volume loadStructure(String volName, World world, Connection zoneConnection) throws SQLException {
Volume volume = new Volume(volName, world);
if (!containsTable(String.format("structure_%d_corners", volName.hashCode() & Integer.MAX_VALUE), zoneConnection)) {
volume = VolumeMapper.loadVolume(volName, name, world);
ZoneVolumeMapper.saveStructure(volume, zoneConnection);
War.war.getLogger().log(Level.INFO, "Stuffed structure {0} into database for warzone {1}", new Object[] {volName, name});
return volume;
}
ZoneVolumeMapper.loadStructure(volume, zoneConnection);
return volume;
}
private boolean containsTable(String table, Connection connection) throws SQLException {
PreparedStatement stmt = connection.prepareStatement("SELECT COUNT(*) AS ct FROM sqlite_master WHERE type = ? AND name = ?");
stmt.setString(1, "table");
stmt.setString(2, table);
ResultSet resultSet = stmt.executeQuery();
try {
return resultSet.next() && resultSet.getInt("ct") > 0;
} finally {
resultSet.close();
stmt.close();
}
}
}

View File

@ -78,7 +78,7 @@ public class RenameZoneCommand extends AbstractZoneMakerCommand {
// Load new warzone
War.war.log("Loading zone " + newName + "...", Level.INFO);
Warzone newZone = WarzoneYmlMapper.load(newName, false);
Warzone newZone = WarzoneYmlMapper.load(newName);
War.war.getWarzones().add(newZone);
try {
newZone.getVolume().loadCorners();

View File

@ -12,11 +12,9 @@ import com.tommytony.war.mapper.WarzoneYmlMapper;
public class RestoreYmlWarzonesJob implements Runnable {
private final List<String> warzones;
private final boolean newWarInstall;
public RestoreYmlWarzonesJob(List<String> warzones, boolean newWarInstall) {
public RestoreYmlWarzonesJob(List<String> warzones) {
this.warzones = warzones;
this.newWarInstall = newWarInstall;
}
public void run() {
@ -25,7 +23,7 @@ public class RestoreYmlWarzonesJob implements Runnable {
for (String warzoneName : this.warzones) {
if (warzoneName != null && !warzoneName.equals("")) {
War.war.log("Loading zone " + warzoneName + "...", Level.INFO);
Warzone zone = WarzoneYmlMapper.load(warzoneName, !this.newWarInstall);
Warzone zone = WarzoneYmlMapper.load(warzoneName);
if (zone != null) { // could have failed, would've been logged already
War.war.getWarzones().add(zone);
try {

View File

@ -48,7 +48,7 @@ public class WarYmlMapper {
// warzones
List<String> warzones = warRootSection.getStringList("war.info.warzones");
RestoreYmlWarzonesJob restoreWarzones = new RestoreYmlWarzonesJob(warzones, newWar); // during conversion, this should execute just after the RestoreTxtWarzonesJob
RestoreYmlWarzonesJob restoreWarzones = new RestoreYmlWarzonesJob(warzones); // during conversion, this should execute just after the RestoreTxtWarzonesJob
if (War.war.getServer().getScheduler().scheduleSyncDelayedTask(War.war, restoreWarzones) == -1) {
War.war.log("Failed to schedule warzone-restore job. No warzone was loaded.", Level.WARNING);
}

View File

@ -2,6 +2,7 @@ package com.tommytony.war.mapper;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
@ -32,7 +33,7 @@ import com.tommytony.war.volume.ZoneVolume;
public class WarzoneYmlMapper {
@SuppressWarnings("deprecation")
public static Warzone load(String name, boolean createNewVolume) {
public static Warzone load(String name) { // removed createNewVolume, as it did nothing
File warzoneTxtFile = new File(War.war.getDataFolder().getPath() + "/warzone-" + name + ".txt");
File warzoneYmlFile = new File(War.war.getDataFolder().getPath() + "/warzone-" + name + ".yml");
@ -275,16 +276,16 @@ public class WarzoneYmlMapper {
}
}
}
if (createNewVolume) {
ZoneVolume zoneVolume = new ZoneVolume(warzone.getName(), world, warzone);
warzone.setVolume(zoneVolume);
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
// monument blocks
for (Monument monument : warzone.getMonuments()) {
try {
monument.setVolume(VolumeMapper.loadVolume(monument.getName(), warzone.getName(), world));
monument.setVolume(warzone.loadStructure(monument.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -293,7 +294,7 @@ public class WarzoneYmlMapper {
// bomb blocks
for (Bomb bomb : warzone.getBombs()) {
try {
bomb.setVolume(VolumeMapper.loadVolume("bomb-" + bomb.getName(), warzone.getName(), world));
bomb.setVolume(warzone.loadStructure("bomb-" + bomb.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -302,7 +303,7 @@ public class WarzoneYmlMapper {
// cake blocks
for (Cake cake : warzone.getCakes()) {
try {
cake.setVolume(VolumeMapper.loadVolume("cake-" + cake.getName(), warzone.getName(), world));
cake.setVolume(warzone.loadStructure("cake-" + cake.getName(), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -312,14 +313,14 @@ public class WarzoneYmlMapper {
for (Team team : warzone.getTeams()) {
for (Location teamSpawn : team.getTeamSpawns()) {
try {
team.setSpawnVolume(teamSpawn, VolumeMapper.loadVolume(team.getName() + team.getTeamSpawns().indexOf(teamSpawn), warzone.getName(), world));
team.setSpawnVolume(teamSpawn, warzone.loadStructure(team.getName() + team.getTeamSpawns().indexOf(teamSpawn), connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
}
if (team.getTeamFlag() != null) {
try {
team.setFlagVolume(VolumeMapper.loadVolume(team.getName() + "flag", warzone.getName(), world));
team.setFlagVolume(warzone.loadStructure(team.getName() + "flag", connection));
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
@ -399,7 +400,7 @@ public class WarzoneYmlMapper {
// create the lobby
Volume lobbyVolume = null;
try {
lobbyVolume = VolumeMapper.loadVolume("lobby", warzone.getName(), lobbyWorld);
lobbyVolume = warzone.loadStructure("lobby", lobbyWorld, connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone lobby", e);
}
@ -443,7 +444,11 @@ public class WarzoneYmlMapper {
(short) floorMaterialSection.getInt("data")));
}
}
try {
connection.close();
} catch (SQLException ignored) {
}
return warzone;
}
@ -649,11 +654,16 @@ public class WarzoneYmlMapper {
flagSection.set("yaw", toIntYaw(teamFlag.getYaw()));
}
}
Connection connection = null;
try {
connection = ZoneVolumeMapper.getZoneConnection(warzone.getVolume(), warzone.getName(), warzone.getWorld());
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to load warzone structures volume", e);
}
// monument blocks
for (Monument monument : warzone.getMonuments()) {
try {
VolumeMapper.save(monument.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(monument.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -662,7 +672,7 @@ public class WarzoneYmlMapper {
// bomb blocks
for (Bomb bomb : warzone.getBombs()) {
try {
VolumeMapper.save(bomb.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(bomb.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -671,7 +681,7 @@ public class WarzoneYmlMapper {
// cake blocks
for (Cake cake : warzone.getCakes()) {
try {
VolumeMapper.save(cake.getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(cake.getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -681,14 +691,14 @@ public class WarzoneYmlMapper {
for (Team team : teams) {
for (Volume volume : team.getSpawnVolumes().values()) {
try {
VolumeMapper.save(volume, warzone.getName());
ZoneVolumeMapper.saveStructure(volume, connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
}
if (team.getFlagVolume() != null) {
try {
VolumeMapper.save(team.getFlagVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(team.getFlagVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
@ -697,12 +707,16 @@ public class WarzoneYmlMapper {
if (warzone.getLobby() != null) {
try {
VolumeMapper.save(warzone.getLobby().getVolume(), warzone.getName());
ZoneVolumeMapper.saveStructure(warzone.getLobby().getVolume(), connection);
} catch (SQLException e) {
War.war.getLogger().log(Level.WARNING, "Failed to save warzone structures volume", e);
}
}
try {
connection.close();
} catch (SQLException ignored) {
}
// Save to disk
try {
File warzoneConfigFile = new File(War.war.getDataFolder().getPath() + "/warzone-" + warzone.getName() + ".yml");

View File

@ -47,6 +47,15 @@ import com.tommytony.war.volume.ZoneVolume;
public class ZoneVolumeMapper {
public static final int DATABASE_VERSION = 2;
/**
* Get a connection to the warzone database, converting blocks if not found.
* @param volume zone volume to load
* @param zoneName warzone to load
* @param world world containing this warzone
* @return an open connection to the sqlite file
* @throws SQLException
*/
public static Connection getZoneConnection(ZoneVolume volume, String zoneName, World world) throws SQLException {
File databaseFile = new File(War.war.getDataFolder(), String.format("/dat/warzone-%s/volume-%s.sl3", zoneName, volume.getName()));
if (!databaseFile.exists()) {
@ -65,8 +74,7 @@ public class ZoneVolumeMapper {
databaseConnection.close();
// Can't load this too-recent format
throw new IllegalStateException("Unsupported zone format (was already converted to version: "
+ version + ", current format: " + DATABASE_VERSION + ")");
throw new IllegalStateException(String.format("Unsupported zone format (was already converted to version: %d, current format: %d)", version, DATABASE_VERSION));
} else if (version < DATABASE_VERSION) {
stmt.close();
@ -204,6 +212,79 @@ public class ZoneVolumeMapper {
return changed;
}
public static int saveStructure(Volume volume, Connection databaseConnection) throws SQLException {
Statement stmt = databaseConnection.createStatement();
stmt.executeUpdate("PRAGMA user_version = " + DATABASE_VERSION);
// Storing zonemaker-inputted name could result in injection or undesirable behavior.
String prefix = String.format("structure_%d", volume.getName().hashCode() & Integer.MAX_VALUE);
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix +
"_blocks (x BIGINT, y BIGINT, z BIGINT, type TEXT, data SMALLINT)");
stmt.executeUpdate("CREATE TABLE IF NOT EXISTS " + prefix +
"_corners (pos INTEGER PRIMARY KEY NOT NULL UNIQUE, x INTEGER NOT NULL, y INTEGER NOT NULL, z INTEGER NOT NULL)");
stmt.executeUpdate("DELETE FROM " + prefix + "_blocks");
stmt.executeUpdate("DELETE FROM " + prefix + "_corners");
stmt.close();
PreparedStatement cornerStmt = databaseConnection
.prepareStatement("INSERT INTO " + prefix + "_corners SELECT 1 AS pos, ? AS x, ? AS y, ? AS z UNION SELECT 2, ?, ?, ?");
cornerStmt.setInt(1, volume.getCornerOne().getBlockX());
cornerStmt.setInt(2, volume.getCornerOne().getBlockY());
cornerStmt.setInt(3, volume.getCornerOne().getBlockZ());
cornerStmt.setInt(4, volume.getCornerTwo().getBlockX());
cornerStmt.setInt(5, volume.getCornerTwo().getBlockY());
cornerStmt.setInt(6, volume.getCornerTwo().getBlockZ());
cornerStmt.executeUpdate();
cornerStmt.close();
PreparedStatement dataStmt = databaseConnection
.prepareStatement("INSERT INTO " + prefix + "_blocks VALUES (?, ?, ?, ?, ?)");
databaseConnection.setAutoCommit(false);
final int batchSize = 1000;
int changed = 0;
for (BlockState block : volume.getBlocks()) {
final Location relLoc = ZoneVolumeMapper.rebase(
volume.getCornerOne(), block.getLocation());
dataStmt.setInt(1, relLoc.getBlockX());
dataStmt.setInt(2, relLoc.getBlockY());
dataStmt.setInt(3, relLoc.getBlockZ());
dataStmt.setString(4, block.getType().toString());
dataStmt.setShort(5, block.getData().toItemStack().getDurability());
dataStmt.addBatch();
if (++changed % batchSize == 0) {
dataStmt.executeBatch();
}
}
dataStmt.executeBatch(); // insert remaining records
databaseConnection.commit();
databaseConnection.setAutoCommit(true);
dataStmt.close();
return changed;
}
public static void loadStructure(Volume volume, Connection databaseConnection) throws SQLException {
String prefix = String.format("structure_%d", volume.getName().hashCode() & Integer.MAX_VALUE);
World world = volume.getWorld();
Statement stmt = databaseConnection.createStatement();
ResultSet cornerQuery = stmt.executeQuery("SELECT * FROM " + prefix + "_corners");
cornerQuery.next();
final Block corner1 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.next();
final Block corner2 = world.getBlockAt(cornerQuery.getInt("x"), cornerQuery.getInt("y"), cornerQuery.getInt("z"));
cornerQuery.close();
volume.setCornerOne(corner1);
volume.setCornerTwo(corner2);
volume.getBlocks().clear();
ResultSet query = stmt.executeQuery("SELECT * FROM " + prefix + "_blocks");
while (query.next()) {
int x = query.getInt("x"), y = query.getInt("y"), z = query.getInt("z");
BlockState modify = corner1.getRelative(x, y, z).getState();
ItemStack data = new ItemStack(Material.valueOf(query.getString("type")), 0, query.getShort("data"));
modify.setType(data.getType());
modify.setData(data.getData());
volume.getBlocks().add(modify);
}
query.close();
stmt.close();
}
/**
* Get total saved blocks for a warzone. This should only be called on nimitz-format warzones.
* @param volume Warzone volume