BlockState Migration-Command

This commit is contained in:
Jascha Starke 2013-09-26 20:29:43 +02:00
parent 0a05ac75a4
commit 6db1664a7e
6 changed files with 318 additions and 26 deletions

View file

@ -26,6 +26,7 @@ public class MainCommand extends BukkitCommand implements IHelpDescribed, IMetho
public MainCommand() {
}
public MainCommand(LimitedCreative plugin) {
super(plugin);
this.plugin = plugin;
}

View file

@ -2,6 +2,7 @@ package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.util.Date;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.Material;
@ -12,6 +13,7 @@ import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.selections.Selection;
import de.jaschastarke.LocaleString;
import de.jaschastarke.bukkit.lib.chat.ChatFormattings;
import de.jaschastarke.bukkit.lib.commands.BukkitCommand;
import de.jaschastarke.bukkit.lib.commands.CommandContext;
import de.jaschastarke.bukkit.lib.commands.CommandException;
@ -20,6 +22,10 @@ import de.jaschastarke.bukkit.lib.commands.IHelpDescribed;
import de.jaschastarke.bukkit.lib.commands.MissingPermissionCommandException;
import de.jaschastarke.bukkit.lib.commands.annotations.IsCommand;
import de.jaschastarke.bukkit.lib.commands.annotations.Usages;
import de.jaschastarke.bukkit.lib.commands.parser.DefinedParameterParser;
import de.jaschastarke.bukkit.lib.database.DBHelper;
import de.jaschastarke.database.DatabaseConfigurationException;
import de.jaschastarke.database.db.Database;
import de.jaschastarke.maven.ArchiveDocComments;
import de.jaschastarke.maven.PluginCommand;
import de.jaschastarke.minecraft.lib.permissions.IAbstractPermission;
@ -27,6 +33,7 @@ import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source;
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid;
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.DBTransaction;
import de.jaschastarke.modularize.ModuleEntry.ModuleState;
/**
* LimitedCreative-BlockState-Command: modify blockstate database to prevent drops of selected blocks (requires WorldEdit)
@ -43,7 +50,8 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed {
this.help = this.getDefaultHelpCommand();
}
public BlockStateCommand(ModBlockStates mod) {
this();
super(mod.getPlugin());
this.help = this.getDefaultHelpCommand();
this.mod = mod;
}
@ -68,7 +76,7 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed {
@Override
public CharSequence[] getUsages() {
return null;
return new String[]{"..."};
}
@Override
@ -80,6 +88,12 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed {
public CharSequence getPackageName() {
return mod.getPlugin().getName() + " - " + mod.getName();
}
public boolean execute(final CommandContext context, final String[] args) throws MissingPermissionCommandException, CommandException {
if (mod.getModuleEntry().getState() != ModuleState.ENABLED)
throw new CommandException("Module " + mod.getName() + " is disabled");
return super.execute(context, args);
}
/**
* Modifies the Block-GameMode-Database and sets all blocks in the selection to the provided gamemode. Set it
@ -89,7 +103,6 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed {
* @throws MissingPermissionCommandException
*/
@IsCommand("set")
//@NeedsPermission("region")
@Usages("<gamemode>")
public boolean setGameMode(final CommandContext context, String... args) throws CommandException, MissingPermissionCommandException {
if (!mod.getPlugin().getServer().getPluginManager().isPluginEnabled("WorldEdit")) {
@ -169,6 +182,60 @@ public class BlockStateCommand extends BukkitCommand implements IHelpDescribed {
});
return true;
}
/**
* Imports BlockState Data from a given Database to the current active Database.
* A Server-Restart is needed after migration!
* Parameters:
* -u --update Don't delete existing records / only overwrite if newer
*/
@IsCommand("migrate")
@Usages("-u <dsn> [username] [password]")
public boolean migrateDatabase(final CommandContext context, String... args) throws CommandException, MissingPermissionCommandException {
DefinedParameterParser params = new DefinedParameterParser(args, new String[]{"debug", "d", "update", "u", "confirm"});
if (params.getArgumentCount() < 1) {// doesn't count parameters
return false;
}
if (Bukkit.getServer().getOnlinePlayers().length > (context.isPlayer() ? 1 : 0)) {
context.responseFormatted(ChatFormattings.ERROR, L("command.blockstate.migrate_useronline_error"));
return true;
}
Database source;
Database target;
try {
if (params.getArgumentCount() < 2)
source = DBHelper.createConnection(params.getArgument(0));
else if (params.getArgumentCount() < 3)
source = DBHelper.createConnection(params.getArgument(0), params.getArgument(1), null);
else
source = DBHelper.createConnection(params.getArgument(0), params.getArgument(1), params.getArgument(2));
target = mod.getPlugin().getDatabaseConnection();
} catch (DatabaseConfigurationException e) {
context.responseFormatted(ChatFormattings.ERROR, L("command.blockstate.migrate_connect_error", e.getMessage()));
return true;
}
if (!params.getFlags().contains("confirm")) {
context.responseFormatted(ChatFormattings.INFO, L("command.blockstate.migrate_confirm", "--confirm"));
return true;
}
mod.getModuleEntry().disable();
DatabaseMigrationThread thread = new DatabaseMigrationThread(mod, context, source, target);
if (params.getFlags().contains("update") || params.getFlags().contains("u")) {
thread.setMode(DatabaseMigrationThread.Mode.UPDATE);
}
if (params.getFlags().contains("debug") || params.getFlags().contains("d")) {
thread.setDebug(true);
}
thread.start();
context.response(L("command.blockstate.migrate_started", source.getType(), target.getType()));
return true;
}
private String L(String msg, Object... args) {
return mod.getPlugin().getLocale().trans(msg, args);

View file

@ -10,23 +10,24 @@ import java.util.List;
import org.bukkit.GameMode;
import org.bukkit.Location;
import de.jaschastarke.bukkit.lib.database.ResultIterator;
import de.jaschastarke.database.Type;
import de.jaschastarke.database.db.Database;
import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.BlockState.Source;
import de.jaschastarke.utils.IDebugLogHolder;
public class DBQueries {
private Database db;
private ModBlockStates mod;
public DBQueries(ModBlockStates mod, Database db) {
this.mod = mod;
private IDebugLogHolder dbg;
public DBQueries(IDebugLogHolder mod, Database db) {
this.dbg = mod;
this.db = db;
}
private PreparedStatement find = null;
public BlockState find(Location loc) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: find: " + loc.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: find: " + loc.toString());
if (find == null) {
find = db.prepare("SELECT * FROM lc_block_state WHERE x = ? AND y = ? AND z = ? AND world = ?");
}
@ -42,15 +43,17 @@ public class DBQueries {
bs.setGameMode(getGameMode(rs));
bs.setPlayerName(rs.getString("player"));
bs.setSource(getSource(rs));
rs.close();
return bs;
}
rs.close();
return null;
}
private PreparedStatement findall = null;
public List<BlockState> findAllIn(DBModel.Cuboid c) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: findAllIn: " + c.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: findAllIn: " + c.toString());
List<BlockState> blocks = new ArrayList<BlockState>();
if (findall == null) {
findall = db.prepare("SELECT * FROM lc_block_state WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ? AND world = ?");
@ -72,16 +75,44 @@ public class DBQueries {
bs.setSource(getSource(rs));
blocks.add(bs);
}
rs.close();
return blocks;
}
public Iterable<BlockState> iterateAllIn(final DBModel.Cuboid c) throws SQLException {
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: iterateAllIn: " + c.toString());
if (findall == null) {
findall = db.prepare("SELECT * FROM lc_block_state WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? AND z >= ? AND z <= ? AND world = ?");
}
findall.setInt(1, c.getMinX());
findall.setInt(2, c.getMaxX());
findall.setInt(3, c.getMinY());
findall.setInt(4, c.getMaxY());
findall.setInt(5, c.getMinZ());
findall.setInt(6, c.getMaxZ());
findall.setString(7, c.getWorld().getUID().toString());
ResultSet rs = findall.executeQuery();
return new ResultIterator<BlockState>(rs) {
@Override
protected BlockState fetch(ResultSet rs) throws SQLException {
BlockState bs = new BlockState();
bs.setLocation(new Location(c.getWorld(), rs.getInt("x"), rs.getInt("y"), rs.getInt("z")));
bs.setDate(rs.getTimestamp("cdate"));
bs.setGameMode(getGameMode(rs));
bs.setPlayerName(rs.getString("player"));
bs.setSource(getSource(rs));
return bs;
}
};
}
private PreparedStatement delete = null;
public boolean delete(BlockState s) throws SQLException {
return delete(s.getLocation());
}
public boolean delete(Location loc) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: delete: " + loc.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: delete: " + loc.toString());
if (delete == null) {
delete = db.prepare("DELETE FROM lc_block_state WHERE x = ? AND y = ? AND z = ? AND world = ?");
}
@ -94,8 +125,8 @@ public class DBQueries {
private PreparedStatement update = null;
public boolean update(BlockState s) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: update: " + s.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: update: " + s.toString());
if (update == null) {
update = db.prepare("UPDATE lc_block_state SET gm = ?, player = ?, cdate = ?, source = ?"+
"WHERE x = ? AND y = ? AND z = ? AND world = ? ");
@ -128,8 +159,8 @@ public class DBQueries {
}
public boolean move(Location oldLoc, Location newLoc) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: move: " + oldLoc.toString() + ", " + newLoc.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: move: " + oldLoc.toString() + ", " + newLoc.toString());
if (move == null) {
move = db.prepare("UPDATE lc_block_state SET x = ?, y = ?, z = ? "+
"WHERE x = ? AND y = ? AND z = ? AND world = ?");
@ -156,8 +187,8 @@ public class DBQueries {
}*/
private PreparedStatement insert = null;
public boolean insert(BlockState s) throws SQLException {
if (mod.isDebug())
mod.getLog().debug("DBQuery: insert: " + s.toString());
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: insert: " + s.toString());
if (insert == null) {
insert = db.prepare("INSERT INTO lc_block_state (x, y, z, world, gm, player, cdate, source)"+
" VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
@ -220,8 +251,8 @@ public class DBQueries {
switch (db.getType()) {
case SQLite:
if (!db.getDDL().tableExists("lc_block_state")) {
if (mod.isDebug())
mod.getLog().debug("DBQuery: initTable SQLite: lc_block_state");
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: initTable SQLite: lc_block_state");
db.execute(
"CREATE TABLE lc_block_state ("+
"x integer,"+
@ -236,14 +267,14 @@ public class DBQueries {
"constraint ck_lc_block_state_gm check (gm in (0,1,2)),"+
"constraint ck_lc_block_state_source check (source in (0,1,2,3,4))"+
")"
);
).close();
db.getLogger().info("Created SQLite-Table: lc_block_state");
}
break;
case MySQL:
if (!db.getDDL().tableExists("lc_block_state")) {
if (mod.isDebug())
mod.getLog().debug("DBQuery: initTable MySQL: lc_block_state");
if (dbg.isDebug())
dbg.getLog().debug("DBQuery: initTable MySQL: lc_block_state");
db.execute(
"CREATE TABLE IF NOT EXISTS lc_block_state ("+
"x INT NOT NULL,"+
@ -256,7 +287,7 @@ public class DBQueries {
"source ENUM('SEED','PLAYER','EDIT','COMMAND','UNKNOWN') NOT NULL,"+
"PRIMARY KEY (x, y, z, world)"+
")"
);
).close();
db.getLogger().info("Created MySQL-Table: lc_block_state");
}
break;

View file

@ -0,0 +1,178 @@
package de.jaschastarke.minecraft.limitedcreative.blockstate;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import de.jaschastarke.bukkit.lib.chat.ChatFormattings;
import de.jaschastarke.bukkit.lib.commands.CommandContext;
import de.jaschastarke.database.db.Database;
import de.jaschastarke.minecraft.limitedcreative.ModBlockStates;
import de.jaschastarke.minecraft.limitedcreative.blockstate.DBModel.Cuboid;
import de.jaschastarke.utils.IDebugLogHolder;
import de.jaschastarke.utils.ISimpleLogger;
public class DatabaseMigrationThread extends Thread implements IDebugLogHolder {
private static final int CHUNK_SIZE = 512;
private ModBlockStates mod;
private CommandContext context;
private Database source;
private Database target;
private Mode mode = Mode.REPLACE;
private boolean debug = false;
public static enum Mode {
REPLACE,
UPDATE
}
public DatabaseMigrationThread(ModBlockStates mod, CommandContext context, Database source, Database target) {
this.mod = mod;
this.context = context;
this.source = source;
this.target = target;
setName("LC BlockState Database-Migration");
setPriority(MIN_PRIORITY);
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
@Override
public boolean isDebug() {
return debug;
}
@Override
public ISimpleLogger getLog() {
return mod.getLog();
}
@Override
public void run() {
try {
if (!target.isInTransaction())
target.startTransaction();
int rowCount = 0;
Connection sourceConnection = source.getConnection();
Connection targetConnection = target.getConnection();
if (mode == Mode.REPLACE) {
targetConnection.createStatement().execute("DELETE FROM lc_block_state");
}
DBQueries sourceDB = new DBQueries(this, source);
DBQueries targetDB = new DBQueries(this, target);
List<WorldSize> worldBounds = new ArrayList<WorldSize>();
ResultSet fetchBounds = sourceConnection.createStatement().executeQuery("SELECT world, MIN(x), MIN(z), MAX(x), MAX(z) FROM lc_block_state GROUP BY world");
while (fetchBounds.next()) {
worldBounds.add(new WorldSize(fetchBounds.getString("world"),
fetchBounds.getInt(2),
fetchBounds.getInt(3),
fetchBounds.getInt(4),
fetchBounds.getInt(5)));
}
fetchBounds.close();
for (WorldSize bounds : worldBounds) {
World world = mod.getPlugin().getServer().getWorld(bounds.getWorld());
if (world != null) {
long time = System.currentTimeMillis();
int itCount = 0;
if (mod.isDebug())
mod.getLog().debug("Processing world " + world.getName() + " with bounds: " + bounds);
for (int x = bounds.getMinX(); x <= bounds.getMaxX(); x += CHUNK_SIZE + 1) {
for (int z = bounds.getMinZ(); z <= bounds.getMaxZ(); z += CHUNK_SIZE + 1) {
Cuboid c = new Cuboid();
c.add(new Location(world, x, 0, z));
c.add(new Location(world, x + CHUNK_SIZE, world.getMaxHeight(), z + CHUNK_SIZE));
System.out.println("Fetching Cuboid: " + c.toString());
for (BlockState bs : sourceDB.iterateAllIn(c)) {
if (mode == Mode.UPDATE) {
BlockState xs = targetDB.find(bs.getLocation());
if (xs == null) {
targetDB.insert(bs);
} else if (xs.getDate().before(bs.getDate())) {
targetDB.update(bs);
}
} else {
targetDB.insert(bs);
}
rowCount++;
itCount++;
}
Thread.yield();
}
}
String region = "Region{world = " + world.getName() + ", x = [" + bounds.getMinX() + "; " + (bounds.getMinX() + CHUNK_SIZE) + "], z = [" + bounds.getMinZ() + "; " + (bounds.getMinZ() + CHUNK_SIZE) + "]}";
mod.getLog().info("Migration processed " + itCount + " BlockStates in " + region + " within " + ((System.currentTimeMillis() - time) / 1000.0) + " seconds");
}
}
target.endTransaction();
context.responseFormatted(ChatFormattings.SUCCESS, L("command.blockstate.migration_finished", rowCount) + " " +
context.getFormatter().formatString(ChatFormattings.ERROR, L("command.blockstate.migration_finished_restart")));
} catch (SQLException e) {
try {
target.revertTransaction();
} catch (SQLException e1) {}
context.responseFormatted(ChatFormattings.ERROR, L("command.blockstate.migration_error", e.getMessage()));
}
}
private String L(String msg, Object... args) {
return mod.getPlugin().getLocale().trans(msg, args);
}
private static class WorldSize {
UUID w;
int minX, minZ, maxX, maxZ;
public WorldSize(String w, int minX, int minZ, int maxX, int maxZ) {
this.w = UUID.fromString(w);
this.minX = minX;
this.minZ = minZ;
this.maxX = maxX;
this.maxZ = maxZ;
}
public String toString() {
World world = Bukkit.getServer().getWorld(w);
String wn = world == null ? w.toString() : world.getName();
return getClass().getSimpleName() + "{world = " + wn + ", minX = " + minX + ", minZ = " + minZ + ", maxX = " + maxX + ", maxZ = " + maxZ + "}";
}
public UUID getWorld() {
return w;
}
public int getMinX() {
return minX;
}
public int getMinZ() {
return minZ;
}
public int getMaxX() {
return maxX;
}
public int getMaxZ() {
return maxZ;
}
}
}

View file

@ -30,6 +30,7 @@ import de.jaschastarke.minecraft.limitedcreative.ModRegions;
import de.jaschastarke.minecraft.limitedcreative.regions.worldguard.FlagList;
import de.jaschastarke.minecraft.limitedcreative.regions.worldguard.FlagValue;
import de.jaschastarke.minecraft.limitedcreative.regions.worldguard.Region;
import de.jaschastarke.modularize.ModuleEntry.ModuleState;
/**
* LimitedCreative-Region-Command: configure creative regions
@ -48,7 +49,8 @@ public class RegionsCommand extends BukkitCommand implements IHelpDescribed {
this.help = this.getDefaultHelpCommand();
}
public RegionsCommand(ModRegions mod) {
this();
super(mod.getPlugin());
this.help = this.getDefaultHelpCommand();
this.mod = mod;
this.wg = (WorldGuardPlugin) mod.getPlugin().getServer().getPluginManager().getPlugin(WorldGuardIntegration.PLUGIN_NAME);
}
@ -62,6 +64,12 @@ public class RegionsCommand extends BukkitCommand implements IHelpDescribed {
public String[] getAliases() {
return new String[]{"/region"};
}
public boolean execute(final CommandContext context, final String[] args) throws MissingPermissionCommandException, CommandException {
if (mod.getModuleEntry().getState() != ModuleState.ENABLED)
throw new CommandException("Module " + mod.getName() + " is disabled");
return super.execute(context, args);
}
/**
* @internal has no effect, as not tested by any command handler

View file

@ -30,6 +30,13 @@ command.blockstate.requires_worldedit: This command requires WorldEdit-Plugin
command.blockstate.worledit_selection_empty: Select a region with WorldEdit first
command.blockstate.command_updated: Successfully updated {0} blocks.
command.blockstate.command_failed: Failed to update blocks. See server.log for details.
command.blockstate.migrate_started: Database migration from {0} to {1} started...
command.blockstate.migrate_connect_error: Connection to database failed with error: {0}
command.blockstate.migration_finished: Database migration of {0} records successful.
command.blockstate.migration_finished_restart: A Server-Restart is required!
command.blockstate.migration_error: Migration failed with error: {0}
command.blockstate.migrate_useronline_error: There are players on the Server. The migration shouldn't be run with active players.
command.blockstate.migrate_confirm: Are you sure you want to start the Migration? It may take a very long time and much CPU. To confirm execution run the command with the following added: {0}
cmdblock.blocked: This command is blocked while in creative mode.
cmdblock.blocked.requires_worldedit: WorlEdit is required to use this command