From c1b0b9738e8b806983d1f0153679ac6b87b0a274 Mon Sep 17 00:00:00 2001 From: Robin Kupper Date: Thu, 10 Mar 2011 12:55:47 +0100 Subject: [PATCH] v0.1 --- .gitignore | 38 +- LogBlock.jar | Bin 44338 -> 0 bytes LogBlock.java | 549 --------------- MANIFEST.MF | 3 + README | 6 +- Rollback.java | 98 --- schema.sql | 21 - .../JDCBPool/ConnectionService.java | 104 +++ .../JDCBPool/JDCConnection.java | 455 +++++++++++++ .../JDCBPool/JDCConnectionDriver.java | 63 ++ .../diddiz/LogBlock/AreaBlockSearch.java | 43 +- .../com/bukkit/diddiz/LogBlock/AreaStats.java | 48 +- src/com/bukkit/diddiz/LogBlock/LogBlock.java | 634 ++++++++++++++++++ .../diddiz/LogBlock/PlayerAreaStats.java | 49 +- .../diddiz/LogBlock/PlayerWorldStats.java | 24 +- src/plugin.yml | 6 + 16 files changed, 1370 insertions(+), 771 deletions(-) delete mode 100755 LogBlock.jar delete mode 100755 LogBlock.java create mode 100644 MANIFEST.MF delete mode 100755 Rollback.java delete mode 100644 schema.sql create mode 100644 src/com/bukkit/bootswithdefer/JDCBPool/ConnectionService.java create mode 100644 src/com/bukkit/bootswithdefer/JDCBPool/JDCConnection.java create mode 100644 src/com/bukkit/bootswithdefer/JDCBPool/JDCConnectionDriver.java rename AreaBlockSearch.java => src/com/bukkit/diddiz/LogBlock/AreaBlockSearch.java (54%) mode change 100755 => 100644 rename AreaStats.java => src/com/bukkit/diddiz/LogBlock/AreaStats.java (61%) mode change 100755 => 100644 create mode 100644 src/com/bukkit/diddiz/LogBlock/LogBlock.java rename PlayerAreaStats.java => src/com/bukkit/diddiz/LogBlock/PlayerAreaStats.java (55%) mode change 100755 => 100644 rename PlayerWorldStats.java => src/com/bukkit/diddiz/LogBlock/PlayerWorldStats.java (75%) mode change 100755 => 100644 create mode 100644 src/plugin.yml diff --git a/.gitignore b/.gitignore index 3677c8b..ea7eba2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,4 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so -*.gpj - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db +/.classpath +/.project +/.settings +/bin \ No newline at end of file diff --git a/LogBlock.jar b/LogBlock.jar deleted file mode 100755 index 3305b71078674337ac67c2429127e88b9e1cc9b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44338 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 diff --git a/LogBlock.java b/LogBlock.java deleted file mode 100755 index 066cca5..0000000 --- a/LogBlock.java +++ /dev/null @@ -1,549 +0,0 @@ -import java.util.concurrent.TimeUnit; -import java.util.concurrent.LinkedBlockingQueue; -import java.text.SimpleDateFormat; - -import java.util.logging.*; -import java.sql.*; -import java.io.*; - -import net.minecraft.server.MinecraftServer; - -public class LogBlock extends Plugin -{ - private static String name = "LogBlock"; - private static int version = 14; - private boolean debug = false; - private String dbDriver = "com.mysql.jdbc.Driver"; - private String dbUrl = ""; - private String dbUsername = ""; - private String dbPassword = ""; - private boolean usehModDb = false; - private int delay = 10; - private int defaultDist = 20; - private int toolID = 270; // 270 is wood pick axe - private int toolblockID = 7; // 78 is adminium - private boolean toolblockRemove = true; - private Consumer consumer = null; - private Block lastface = null; - - private LinkedBlockingQueue bqueue = new LinkedBlockingQueue(); - - static final Logger log = Logger.getLogger("Minecraft"); - - static final Logger lblog = Logger.getLogger(name); - - public void enable() - { - new VersionCheck(name, version); - - PropertiesFile properties = new PropertiesFile("logblock.properties"); - try { - debug = properties.getBoolean("debug", false); - usehModDb = properties.getBoolean("use-hmod-db", false); - dbDriver = properties.getString("driver", "com.mysql.jdbc.Driver"); - dbUrl = properties.getString("url", "jdbc:mysql://localhost:3306/db"); - dbUsername = properties.getString("username", "user"); - dbPassword = properties.getString("password", "pass"); - delay = properties.getInt("delay", 6); - toolID = properties.getInt("tool-id", 270); - toolblockID = properties.getInt("tool-block-id", 7); - toolblockRemove = properties.getBoolean("tool-block-remove", true); - defaultDist = properties.getInt("default-distance", 20); - } catch (Exception ex) { - log.log(Level.SEVERE, name + ": exception while reading from logblock.properties", ex); - return; - } - try { - if (!usehModDb) - new JDCConnectionDriver(dbDriver, dbUrl, dbUsername, dbPassword); - } catch (Exception ex) { - log.log(Level.SEVERE, name + ": exception while creation database connection pool", ex); - return; - } - - if (!checkTables()) - { - log.log(Level.SEVERE, name + ": errors while loading, check logs for more information."); - return; - } - - consumer = new Consumer(); - new Thread(consumer).start(); - etc.getInstance().addCommand("/lb", " - LogBlock display command."); - etc.getInstance().addCommand("/rollback", " - LogBlock Rollback command."); - log.info(name + " v" + version + " Plugin Enabled."); - } - - public void disable() - { - if (consumer != null) - consumer.stop(); - consumer = null; - etc.getInstance().removeCommand("/lb"); - etc.getInstance().removeCommand("/rollback"); - log.info(name + " v" + version + " Plugin Disabled."); - } - - public void initialize() - { - try { - FileHandler lbfh = new FileHandler(name + ".log", true); - lbfh.setFormatter(new LogFormatter()); - lblog.addHandler(lbfh); - } catch (IOException ex) { - log.info(name + " unable to create logger"); - } - - LBListener listener = new LBListener(); - etc.getLoader().addListener(PluginLoader.Hook.COMMAND, listener, this, PluginListener.Priority.LOW); - etc.getLoader().addListener(PluginLoader.Hook.BLOCK_RIGHTCLICKED, listener, this, PluginListener.Priority.LOW); - etc.getLoader().addListener(PluginLoader.Hook.BLOCK_PLACE, listener, this, PluginListener.Priority.LOW); - etc.getLoader().addListener(PluginLoader.Hook.BLOCK_BROKEN, listener, this, PluginListener.Priority.LOW); - etc.getLoader().addListener(PluginLoader.Hook.SIGN_CHANGE, listener, this, PluginListener.Priority.LOW); - etc.getLoader().addListener(PluginLoader.Hook.ITEM_USE, listener, this, PluginListener.Priority.LOW); - } - - private Connection getConnection() throws SQLException - { - if (usehModDb) - return etc.getSQLConnection(); - return DriverManager.getConnection("jdbc:jdc:jdcpool"); - } - - private boolean checkTables() - { - Connection conn = null; - ResultSet rs = null; - try { - conn = getConnection(); - DatabaseMetaData dbm = conn.getMetaData(); - rs = dbm.getTables(null, null, "blocks", null); - if (!rs.next()) - { - log.log(Level.SEVERE, name + " blocks table doesn't exist."); - return false; - } - rs = dbm.getTables(null, null, "extra", null); - if (!rs.next()) - { - log.log(Level.SEVERE, name + " extra table doesn't exist."); - return false; - } - return true; - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception", ex); - } finally { - try { - if (rs != null) - rs.close(); - if (conn != null) - conn.close(); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception on close", ex); - } - } - return false; - } - - private void showBlockHistory(Player player, Block b) - { - player.sendMessage(Colors.Blue + "Block history (" + b.getX() + ", " + b.getY() + ", " + b.getZ() + "): "); - boolean hist = false; - Connection conn = null; - PreparedStatement ps = null; - ResultSet rs = null; - Timestamp date; - SimpleDateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss"); - - try { - conn = getConnection(); - conn.setAutoCommit(false); - ps = conn.prepareStatement("SELECT * from blocks left join extra using (id) where y = ? and x = ? and z = ? order by date desc limit 10", Statement.RETURN_GENERATED_KEYS); - ps.setInt(1, b.getY()); - ps.setInt(2, b.getX()); - ps.setInt(3, b.getZ()); - rs = ps.executeQuery(); - while (rs.next()) - { - date = rs.getTimestamp("date"); - String datestr = formatter.format(date); - String msg = datestr + " " + rs.getString("player") + " "; - if (rs.getInt("type") == 0) - msg = msg + "destroyed " + etc.getDataSource().getItem(rs.getInt("replaced")); - else if (rs.getInt("replaced") == 0) - { - if (rs.getInt("type") == 323) // sign - msg = msg + "created " + rs.getString("extra"); - else - msg = msg + "created " + etc.getDataSource().getItem(rs.getInt("type")); - } - else - msg = msg + "replaced " + etc.getDataSource().getItem(rs.getInt("replaced")) + " with " + etc.getDataSource().getItem(rs.getInt("type")); - player.sendMessage(Colors.Gold + msg); - hist = true; - } - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception", ex); - } finally { - try { - if (rs != null) - rs.close(); - if (ps != null) - ps.close(); - if (conn != null) - conn.close(); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception on close", ex); - } - } - if (!hist) - player.sendMessage(Colors.Blue + "None."); - } - - private void queueBlock(Player player, Block before, Block after) - { - Block b = null; - int typeA = 0; - int typeB = 0; - if (after != null) - { - typeA = after.getType(); - b = after; - } - if (before != null) - { - typeB = before.getType(); - b = before; - } - - if (b == null || typeA < 0 || typeB < 0) - return; - - BlockRow row = new BlockRow(player.getName(), typeB, typeA, b.getX(), b.getY(), b.getZ()); - boolean result = bqueue.offer(row); - if (debug) - lblog.info(row.toString()); - if (!result) - log.info(name + " failed to queue block for " + player.getName()); - } - - private void queueSign(Player player, Sign sign) - { - int type = etc.getDataSource().getItem("sign"); - BlockRow row = new BlockRow(player.getName(), 0, type, sign.getX(), sign.getY(), sign.getZ()); - - String text = "sign"; - for (int i=0; i < 4; i++) - text = text + " [" + sign.getText(i) + "]"; - row.addExtra(text); - - boolean result = bqueue.offer(row); - if (debug) - lblog.info(row.toString()); - if (!result) - log.info(name + " failed to queue block for " + player.getName()); - } - - private int parseTimeSpec(String ts) - { - String[] split = ts.split(" "); - - if (split.length < 2) - return 0; - - int min; - try { - min = Integer.parseInt(split[0]); - } catch (NumberFormatException ex) { - return 0; - } - - if (split[1].startsWith("hour")) - min *= 60; - else if (split[1].startsWith("day")) - min *= (60*24); - - return min; - } - - public class LBListener extends PluginListener // start - { - public boolean onCommand(Player player, String[] split) - { - if (!player.canUseCommand(split[0])) - return false; - - if (split[0].equalsIgnoreCase("/lb")) - { - Connection conn; - try { - conn = getConnection(); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception", ex); - player.sendMessage(Colors.Rose + "Error, check server logs."); - return true; - } - - if (split.length == 1) { - AreaStats th = new AreaStats(conn, player, defaultDist); - new Thread(th).start(); - return true; - } - - if (split.length == 2) { - if (split[1].equalsIgnoreCase("world")) { - PlayerWorldStats th = new PlayerWorldStats(conn, player); - new Thread(th).start(); - return true; - } - player.sendMessage(Colors.Rose + "Incorrect usage."); - return true; - } - - if (split[1].equalsIgnoreCase("player")) { - PlayerAreaStats th = new PlayerAreaStats(conn, player, split[2], defaultDist); - new Thread(th).start(); - return true; - } - - if (split[1].equalsIgnoreCase("area")) { - AreaStats th = new AreaStats(conn, player, Integer.parseInt(split[2])); - new Thread(th).start(); - return true; - } - - if (split[1].equalsIgnoreCase("block")) { - int type = etc.getDataSource().getItem(split[2]); - AreaBlockSearch th = new AreaBlockSearch(conn, player, type, defaultDist); - new Thread(th).start(); - return true; - } - - player.sendMessage(Colors.Rose + "Incorrect usage."); - return true; - } - - if (split[0].equalsIgnoreCase("/rollback")) - { - int minutes; - String name; - - if (split.length < 3) - { - player.sendMessage(Colors.Rose + "Usate: /rollback [player] [time spec]"); - return true; - } - name = split[1]; - minutes = parseTimeSpec(etc.combineSplit(2, split, " ")); - - player.sendMessage(Colors.Rose + "Rolling back " + name + " by " + minutes + " minutes."); - - Connection conn; - try { - conn = getConnection(); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception", ex); - player.sendMessage(Colors.Rose + "Error, check server logs."); - return true; - } - Rollback rb = new Rollback(conn, name, minutes); - - player.sendMessage(Colors.Rose + "Edit count: " + rb.count()); - - new Thread(rb).start(); - return true; - } - - return false; - } - - public void onBlockRightClicked(Player player, Block blockClicked, Item item) - { - if (item.getItemId() == toolID && player.canUseCommand("/blockhistory")) - { - showBlockHistory(player, blockClicked); - return; - } - - lastface = blockClicked.getFace(blockClicked.getFaceClicked()); - if (debug) - lblog.info("onBlockRightClicked: clicked " + blockClicked.getType() + " item " + item.getItemId() + " face " + blockClicked.getFace(blockClicked.getFaceClicked()).getType()); - } - - public boolean onBlockPlace(Player player, Block blockPlaced, Block blockClicked, Item itemInHand) - { - if (itemInHand.getItemId() == toolblockID && player.canUseCommand("/blockhistory")) - { - showBlockHistory(player, blockPlaced); - if (toolblockRemove) - return true; - return false; - } - - if (debug) - lblog.info("onBlockPlace: placed " + blockPlaced.getType() + " clicked " + blockClicked.getType() + " item " + itemInHand.getItemId()); - - queueBlock(player, lastface, blockPlaced); - return false; - } - - public boolean onBlockBreak(Player player, Block block) - { - queueBlock(player, block, null); - return false; - } - - public boolean onSignChange(Player player, Sign sign) - { - queueSign(player, sign); - return false; - } - - public boolean onItemUse(Player player, Block blockPlaced, Block blockClicked, Item item) - { - if (item.getItemId() != 326 && item.getItemId() != 327) // water and lava buckets - return false; - - queueBlock(player, lastface, blockPlaced); - if (debug) - lblog.info("onItemUse: placed " + blockPlaced.getType() + " clicked " + blockClicked.getType() + " item " + item.getItemId()); - - return false; - } - } // end LBListener - - private class Consumer implements Runnable // start - { - private boolean stop = false; - Consumer() { stop = false; } - public void stop() { stop = true; } - public void run() - { - PreparedStatement ps = null; - Connection conn = null; - BlockRow b; - - while (!stop) - { - long start = System.currentTimeMillis()/1000L; - int count = 0; - - if (bqueue.size() > 100) - log.info(name + " queue size " + bqueue.size()); - -// if (debug) -// lblog.info("Running DB thread at " + start); - - try { - conn = getConnection(); - conn.setAutoCommit(false); - while (count < 100 && start+delay > (System.currentTimeMillis()/1000L)) - { -// if (debug) -// lblog.info("Loop DB thread at " + (System.currentTimeMillis()/1000L)); - - b = bqueue.poll(1L, TimeUnit.SECONDS); - - if (b == null) - continue; - //b.log(); - ps = conn.prepareStatement("INSERT INTO blocks (date, player, replaced, type, x, y, z) VALUES (now(),?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); - ps.setString(1, b.name); - ps.setInt(2, b.replaced); - ps.setInt(3, b.type); - ps.setInt(4, b.x); - ps.setInt(5, b.y); - ps.setInt(6, b.z); - ps.executeUpdate(); - - if (b.extra != null) - { - ResultSet keys = ps.getGeneratedKeys(); - keys.next(); - int key = keys.getInt(1); - - ps = conn.prepareStatement("INSERT INTO extra (id, extra) values (?,?)"); - ps.setInt(1, key); - ps.setString(2, b.extra); - ps.executeUpdate(); - } - - count++; - } - if (debug && count > 0) - lblog.info("Commiting " + count + " inserts."); - conn.commit(); - } catch (InterruptedException ex) { - log.log(Level.SEVERE, name + " interrupted exception", ex); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception", ex); - } finally { - try { - if (ps != null) - ps.close(); - if (conn != null) - conn.close(); - } catch (SQLException ex) { - log.log(Level.SEVERE, name + " SQL exception on close", ex); - } - } - } - } - } // end LBDB - - private class BlockRow // start - { - public String name; - public int replaced, type; - public int x, y, z; - public String extra; - - BlockRow(String name, int replaced, int type, int x, int y, int z) - { - this.name = name; - this.replaced = replaced; - this.type = type; - this.x = x; - this.y = y; - this.z = z; - this.extra = null; - } - - public void addExtra(String extra) - { - this.extra = extra; - } - - public String toString() - { - return("name: " + name + " before type: " + replaced + " type: " + type + " x: " + x + " y: " + y + " z: " + z); - } - } // end BlockRow - - private class Result // start - { - public String player; - public int created; - public int destroyed; - - Result(String player, int c, int d) - { - this.player = player; - this.created = c; - this.destroyed = d; - } - - public String toString() - { - return(String.format("%-6d %-6d %s", created, destroyed, player)); - } - } // end Result - - private class LogFormatter extends Formatter //start - { - public String format(LogRecord rec) - { - return formatMessage(rec) + "\n"; - } - } // end LogFormatter -} // end LogBlock diff --git a/MANIFEST.MF b/MANIFEST.MF new file mode 100644 index 0000000..d8ea047 --- /dev/null +++ b/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: ../mysql-connector-java-bin.jar + diff --git a/README b/README index e320bb4..ec9042c 100644 --- a/README +++ b/README @@ -1,4 +1,2 @@ -See http://forum.hey0.net for information about these plugins. - -To compile some packages are required (VersionCheck, JDBCPool) -that are available here: https://github.com/bootswithdefer/boots-plugins/ +This plugin logs block creates and destroys to a MySQL database. It can be used as an anti-griefing tool to find out who made a particular edit, or even roll back changes by certain players. +Originally written by bootswithdefer for hMod ported to Bukkit by me, because of impossibleness to identfy griefers. Due to BigBrother also did't work, I was forced to do it myself. The honor belongs to bootswithdefer for the sourcecode, I only spended nearly 8 hours to transcribe. All functions except sign text logging shold work as in hMod. The use of permissions plugin is possible, but not necessary. \ No newline at end of file diff --git a/Rollback.java b/Rollback.java deleted file mode 100755 index 2431f7a..0000000 --- a/Rollback.java +++ /dev/null @@ -1,98 +0,0 @@ -import java.util.concurrent.LinkedBlockingQueue; - -import java.util.logging.*; -import java.sql.*; - -public class Rollback implements Runnable -{ - static final Logger log = Logger.getLogger("Minecraft"); - private LinkedBlockingQueue edits = new LinkedBlockingQueue(); - - Rollback(Connection conn, String name, int minutes) - { - String query = "select type, replaced, x, y, z from blocks where player = ? and date > date_sub(now(), interval ? minute) order by date desc"; - PreparedStatement ps = null; - ResultSet rs = null; - edits.clear(); - - try { - conn.setAutoCommit(false); - ps = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS); - ps.setString(1, name); - ps.setInt(2, minutes); - rs = ps.executeQuery(); - - while (rs.next()) - { - Edit e = new Edit(rs.getInt("type"), rs.getInt("replaced"), rs.getInt("x"), rs.getInt("y"), rs.getInt("z")); - edits.offer(e); - } - } catch (SQLException ex) { - log.log(Level.SEVERE, this.getClass().getName() + " SQL exception", ex); - } finally { - try { - if (rs != null) - rs.close(); - if (ps != null) - ps.close(); - if (conn != null) - conn.close(); - } catch (SQLException ex) { - log.log(Level.SEVERE, this.getClass().getName() + " SQL exception on close", ex); - } - } - - } - - public int count() - { - return edits.size(); - } - - public void run() - { - Edit e = edits.poll(); - - while (e != null) - { - e.perform(); - e.log(); - e = edits.poll(); - } - } - - private class Edit - { - int type, replaced; - int x, y, z; - - Edit(int type, int replaced, int x, int y, int z) - { - this.type = type; - this.replaced = replaced; - this.x = x; - this.y = y; - this.z = z; - } - - public void perform() - { - if (etc.getServer().getBlockIdAt(x, y, z) == type) - { - if (etc.getServer().setBlockAt(replaced, x, y, z)) - log.info("R (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); - else - log.info("r (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); - } - } - - public void log() - { - int current = etc.getServer().getBlockIdAt(x, y, z); - if (current == type) - log.info("+ (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); - else - log.info("- (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); - } - } -} \ No newline at end of file diff --git a/schema.sql b/schema.sql deleted file mode 100644 index 7562e2e..0000000 --- a/schema.sql +++ /dev/null @@ -1,21 +0,0 @@ -CREATE TABLE `blocks` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `player` varchar(32) NOT NULL DEFAULT '-', - `replaced` int(11) NOT NULL DEFAULT '0', - `type` int(11) NOT NULL DEFAULT '0', - `x` int(11) NOT NULL DEFAULT '0', - `y` int(11) NOT NULL DEFAULT '0', - `z` int(11) NOT NULL DEFAULT '0', - PRIMARY KEY (`id`), - KEY `coords` (`y`,`x`,`z`), - KEY `type` (`type`), - KEY `replaced` (`replaced`), - KEY `player` (`player`) -); - -CREATE TABLE `extra` ( - `id` int(11) NOT NULL, - `extra` text, - PRIMARY KEY (`id`) -); diff --git a/src/com/bukkit/bootswithdefer/JDCBPool/ConnectionService.java b/src/com/bukkit/bootswithdefer/JDCBPool/ConnectionService.java new file mode 100644 index 0000000..3466cce --- /dev/null +++ b/src/com/bukkit/bootswithdefer/JDCBPool/ConnectionService.java @@ -0,0 +1,104 @@ +package com.bukkit.bootswithdefer.JDCBPool; + +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.util.Enumeration; +import java.util.Vector; + +/** + * Purpose:Realizes a connection pool for all JDBC connections.
+ * Description:http://java.sun.com/developer/onlineTraining/Programming/JDCBook/ + * conpool.html
+ * Copyright:Licensed under the Apache License, Version 2.0. + * http://www.apache.org/licenses/LICENSE-2.0
+ * Company:SIMPL
+ * + * @author schneimi + * @version $Id$
+ * @link http://code.google.com/p/simpl09/ + */ +public class ConnectionService { + private Vector connections; + private String url, user, password; + final private long timeout = 60000; + private ConnectionReaper reaper; + final private int poolsize = 10; + + public ConnectionService(String url, String user, String password) { + this.url = url; + this.user = user; + this.password = password; + connections = new Vector(poolsize); + reaper = new ConnectionReaper(this); + reaper.start(); + } + + public synchronized void reapConnections() { + long stale = System.currentTimeMillis() - timeout; + Enumeration connlist = connections.elements(); + + while ((connlist != null) && (connlist.hasMoreElements())) { + JDCConnection conn = connlist.nextElement(); + + if ((conn.inUse()) && (stale > conn.getLastUse()) && (!conn.validate())) { + removeConnection(conn); + } + } + } + + public synchronized void closeConnections() { + Enumeration connlist = connections.elements(); + + while ((connlist != null) && (connlist.hasMoreElements())) { + JDCConnection conn = connlist.nextElement(); + removeConnection(conn); + } + } + + private synchronized void removeConnection(JDCConnection conn) { + connections.removeElement(conn); + } + + public synchronized Connection getConnection() throws SQLException { + JDCConnection c; + + for (int i = 0; i < connections.size(); i++) { + c = connections.elementAt(i); + if (c.lease()) { + return c; + } + } + + Connection conn = DriverManager.getConnection(url, user, password); + c = new JDCConnection(conn, this); + c.lease(); + connections.addElement(c); + + return c.getConnection(); + } + + public synchronized void returnConnection(JDCConnection conn) { + conn.expireLease(); + } +} + +class ConnectionReaper extends Thread { + private ConnectionService pool; + private final long delay = 300000; + + ConnectionReaper(ConnectionService pool) { + this.pool = pool; + } + + @Override + public void run() { + while (true) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + } + pool.reapConnections(); + } + } +} diff --git a/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnection.java b/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnection.java new file mode 100644 index 0000000..843d44c --- /dev/null +++ b/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnection.java @@ -0,0 +1,455 @@ +package com.bukkit.bootswithdefer.JDCBPool; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Map; +import java.util.Properties; + +/** + * Purpose:Wrapper for JDBCConnection.
+ * Description:http://java.sun.com/developer/onlineTraining/Programming/JDCBook/ + * conpool.html
+ * Copyright:Licensed under the Apache License, Version 2.0. + * http://www.apache.org/licenses/LICENSE-2.0
+ * Company:SIMPL
+ * + * @author schneimi + * @version $Id$
+ * @link http://code.google.com/p/simpl09/ + */ +public class JDCConnection implements Connection { + private ConnectionService pool; + private Connection conn; + private boolean inuse; + private long timestamp; + + public JDCConnection(Connection conn, ConnectionService pool) { + this.conn = conn; + this.pool = pool; + this.inuse = false; + this.timestamp = 0; + } + + public synchronized boolean lease() { + if (inuse) { + return false; + } else { + inuse = true; + timestamp = System.currentTimeMillis(); + return true; + } + } + + public boolean validate() { + try { + conn.getMetaData(); + } catch (Exception e) { + return false; + } + return true; + } + + public boolean inUse() { + return inuse; + } + + public long getLastUse() { + return timestamp; + } + + public void close() throws SQLException { + pool.returnConnection(this); + } + + protected void expireLease() { + inuse = false; + } + + protected Connection getConnection() { + return conn; + } + + public PreparedStatement prepareStatement(String sql) throws SQLException { + return conn.prepareStatement(sql); + } + + public CallableStatement prepareCall(String sql) throws SQLException { + return conn.prepareCall(sql); + } + + public Statement createStatement() throws SQLException { + return conn.createStatement(); + } + + public String nativeSQL(String sql) throws SQLException { + return conn.nativeSQL(sql); + } + + public void setAutoCommit(boolean autoCommit) throws SQLException { + conn.setAutoCommit(autoCommit); + } + + public boolean getAutoCommit() throws SQLException { + return conn.getAutoCommit(); + } + + public void commit() throws SQLException { + conn.commit(); + } + + public void rollback() throws SQLException { + conn.rollback(); + } + + public boolean isClosed() throws SQLException { + return conn.isClosed(); + } + + public DatabaseMetaData getMetaData() throws SQLException { + return conn.getMetaData(); + } + + public void setReadOnly(boolean readOnly) throws SQLException { + conn.setReadOnly(readOnly); + } + + public boolean isReadOnly() throws SQLException { + return conn.isReadOnly(); + } + + public void setCatalog(String catalog) throws SQLException { + conn.setCatalog(catalog); + } + + public String getCatalog() throws SQLException { + return conn.getCatalog(); + } + + public void setTransactionIsolation(int level) throws SQLException { + conn.setTransactionIsolation(level); + } + + public int getTransactionIsolation() throws SQLException { + return conn.getTransactionIsolation(); + } + + public SQLWarning getWarnings() throws SQLException { + return conn.getWarnings(); + } + + public void clearWarnings() throws SQLException { + conn.clearWarnings(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createArrayOf(java.lang.String, java.lang.Object[]) + */ + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return conn.createArrayOf(typeName, elements); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createBlob() + */ + @Override + public Blob createBlob() throws SQLException { + return createBlob(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createClob() + */ + @Override + public Clob createClob() throws SQLException { + return conn.createClob(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createNClob() + */ + @Override + public NClob createNClob() throws SQLException { + return conn.createNClob(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createSQLXML() + */ + @Override + public SQLXML createSQLXML() throws SQLException { + return conn.createSQLXML(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createStatement(int, int) + */ + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) + throws SQLException { + return conn.createStatement(resultSetType, resultSetConcurrency); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createStatement(int, int, int) + */ + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, + int resultSetHoldability) throws SQLException { + return conn + .createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#createStruct(java.lang.String, java.lang.Object[]) + */ + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return conn.createStruct(typeName, attributes); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#getClientInfo() + */ + @Override + public Properties getClientInfo() throws SQLException { + return conn.getClientInfo(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#getClientInfo(java.lang.String) + */ + @Override + public String getClientInfo(String name) throws SQLException { + return conn.getClientInfo(name); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#getHoldability() + */ + @Override + public int getHoldability() throws SQLException { + return conn.getHoldability(); + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#getTypeMap() + */ + @Override + public Map> getTypeMap() throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#isValid(int) + */ + @Override + public boolean isValid(int timeout) throws SQLException { + // TODO Auto-generated method stub + return false; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareCall(java.lang.String, int, int) + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, + int resultSetConcurrency) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareCall(java.lang.String, int, int, int) + */ + @Override + public CallableStatement prepareCall(String sql, int resultSetType, + int resultSetConcurrency, int resultSetHoldability) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareStatement(java.lang.String, int) + */ + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) + throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareStatement(java.lang.String, int[]) + */ + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) + throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareStatement(java.lang.String, java.lang.String[]) + */ + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) + throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareStatement(java.lang.String, int, int) + */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, + int resultSetConcurrency) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#prepareStatement(java.lang.String, int, int, int) + */ + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, + int resultSetConcurrency, int resultSetHoldability) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#releaseSavepoint(java.sql.Savepoint) + */ + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#rollback(java.sql.Savepoint) + */ + @Override + public void rollback(Savepoint savepoint) throws SQLException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setClientInfo(java.util.Properties) + */ + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setClientInfo(java.lang.String, java.lang.String) + */ + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setHoldability(int) + */ + @Override + public void setHoldability(int holdability) throws SQLException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setSavepoint() + */ + @Override + public Savepoint setSavepoint() throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setSavepoint(java.lang.String) + */ + @Override + public Savepoint setSavepoint(String name) throws SQLException { + // TODO Auto-generated method stub + return null; + } + + /* + * (non-Javadoc) + * @see java.sql.Connection#setTypeMap(java.util.Map) + */ + @Override + public void setTypeMap(Map> map) throws SQLException { + // TODO Auto-generated method stub + + } + + /* + * (non-Javadoc) + * @see java.sql.Wrapper#isWrapperFor(java.lang.Class) + */ + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + // TODO Auto-generated method stub + return false; + } + + /* + * (non-Javadoc) + * @see java.sql.Wrapper#unwrap(java.lang.Class) + */ + @Override + public T unwrap(Class iface) throws SQLException { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnectionDriver.java b/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnectionDriver.java new file mode 100644 index 0000000..054908a --- /dev/null +++ b/src/com/bukkit/bootswithdefer/JDCBPool/JDCConnectionDriver.java @@ -0,0 +1,63 @@ +package com.bukkit.bootswithdefer.JDCBPool; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.DriverPropertyInfo; +import java.sql.SQLException; +import java.util.Properties; + +/** + * Purpose:Wrapper for JDBCConnectionDriver.
+ * Description:http://java.sun.com/developer/onlineTraining/Programming/JDCBook/ + * conpool.html
+ * Copyright:Licensed under the Apache License, Version 2.0. + * http://www.apache.org/licenses/LICENSE-2.0
+ * Company: SIMPL
+ * + * @author schneimi + * @version $Id: JDCConnectionDriver.java 1224 2010-04-28 14:17:34Z + * michael.schneidt@arcor.de $
+ * @link http://code.google.com/p/simpl09/ + */ +public class JDCConnectionDriver implements Driver { + public static final String URL_PREFIX = "jdbc:jdc:"; + private static final int MAJOR_VERSION = 1; + private static final int MINOR_VERSION = 0; + private ConnectionService pool; + + public JDCConnectionDriver(String driver, String url, String user, String password) + throws ClassNotFoundException, InstantiationException, IllegalAccessException, + SQLException { + DriverManager.registerDriver(this); + Class.forName(driver).newInstance(); + pool = new ConnectionService(url, user, password); + } + + public Connection connect(String url, Properties props) throws SQLException { + if (!url.startsWith(JDCConnectionDriver.URL_PREFIX)) { + return null; + } + return pool.getConnection(); + } + + public boolean acceptsURL(String url) { + return url.startsWith(JDCConnectionDriver.URL_PREFIX); + } + + public int getMajorVersion() { + return JDCConnectionDriver.MAJOR_VERSION; + } + + public int getMinorVersion() { + return JDCConnectionDriver.MINOR_VERSION; + } + + public DriverPropertyInfo[] getPropertyInfo(String str, Properties props) { + return new DriverPropertyInfo[0]; + } + + public boolean jdbcCompliant() { + return false; + } +} diff --git a/AreaBlockSearch.java b/src/com/bukkit/diddiz/LogBlock/AreaBlockSearch.java old mode 100755 new mode 100644 similarity index 54% rename from AreaBlockSearch.java rename to src/com/bukkit/diddiz/LogBlock/AreaBlockSearch.java index 90a8812..0326ab8 --- a/AreaBlockSearch.java +++ b/src/com/bukkit/diddiz/LogBlock/AreaBlockSearch.java @@ -1,9 +1,18 @@ -import java.util.HashSet; -import java.util.HashMap; -import java.text.SimpleDateFormat; +package com.bukkit.diddiz.LogBlock; -import java.util.logging.*; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; public class AreaBlockSearch implements Runnable { @@ -35,15 +44,15 @@ public class AreaBlockSearch implements Runnable ps = conn.prepareStatement("SELECT * from blocks where (type = ? or replaced = ?) and y > ? and y < ? and x > ? and x < ? and z > ? and z < ? order by date desc limit 10", Statement.RETURN_GENERATED_KEYS); ps.setInt(1, type); ps.setInt(2, type); - ps.setInt(3, (int)(location.y) - size); - ps.setInt(4, (int)(location.y) + size); - ps.setInt(5, (int)(location.x) - size); - ps.setInt(6, (int)(location.x) + size); - ps.setInt(7, (int)(location.z) - size); - ps.setInt(8, (int)(location.z) + size); + ps.setInt(3, location.getBlockY() - size); + ps.setInt(4, location.getBlockY() + size); + ps.setInt(5, location.getBlockX() - size); + ps.setInt(6, location.getBlockX() + size); + ps.setInt(7, location.getBlockZ() - size); + ps.setInt(8, location.getBlockZ() + size); rs = ps.executeQuery(); - player.sendMessage(Colors.Blue + "Block history within " + size + " blocks of " + (int)(location.x) + ", " + (int)(location.y) + ", " + (int)(location.z) + ": "); + player.sendMessage("§3Block history within " + size + " blocks of " + location.getBlockX() + ", " + location.getBlockY() + ", " + location.getBlockZ() + ": "); while (rs.next()) { @@ -51,12 +60,12 @@ public class AreaBlockSearch implements Runnable String datestr = formatter.format(date); String msg = datestr + " " + rs.getString("player") + " (" + rs.getInt("x") + ", " + rs.getInt("y") + ", " + rs.getInt("z") + ") "; if (rs.getInt("type") == 0) - msg = msg + "destroyed " + etc.getDataSource().getItem(rs.getInt("replaced")); + msg = msg + "destroyed " + Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' '); else if (rs.getInt("replaced") == 0) - msg = msg + "created " + etc.getDataSource().getItem(rs.getInt("type")); + msg = msg + "created " + Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' '); else - msg = msg + "replaced " + etc.getDataSource().getItem(rs.getInt("replaced")) + " with " + etc.getDataSource().getItem(rs.getInt("type")); - player.sendMessage(Colors.Gold + msg); + msg = msg + "replaced " + Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' ') + " with " + Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' '); + player.sendMessage("§6" + msg); hist = true; } } catch (SQLException ex) { @@ -74,6 +83,6 @@ public class AreaBlockSearch implements Runnable } } if (!hist) - player.sendMessage(Colors.Blue + "None."); + player.sendMessage("§3None."); } } diff --git a/AreaStats.java b/src/com/bukkit/diddiz/LogBlock/AreaStats.java old mode 100755 new mode 100644 similarity index 61% rename from AreaStats.java rename to src/com/bukkit/diddiz/LogBlock/AreaStats.java index 5192f12..6d9c3aa --- a/AreaStats.java +++ b/src/com/bukkit/diddiz/LogBlock/AreaStats.java @@ -1,8 +1,16 @@ -import java.util.HashSet; -import java.util.HashMap; +package com.bukkit.diddiz.LogBlock; -import java.util.logging.*; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.entity.Player; public class AreaStats implements Runnable { @@ -29,12 +37,12 @@ public class AreaStats implements Runnable try { conn.setAutoCommit(false); ps = conn.prepareStatement("SELECT player, count(player) as num from blocks where type > 0 and y > ? and y < ? and x > ? and x < ? and z > ? and z < ? group by player order by count(player) desc limit 10", Statement.RETURN_GENERATED_KEYS); - ps.setInt(1, (int)player.getY()-size); - ps.setInt(2, (int)player.getY()+size); - ps.setInt(3, (int)player.getX()-size); - ps.setInt(4, (int)player.getX()+size); - ps.setInt(5, (int)player.getZ()-size); - ps.setInt(6, (int)player.getZ()+size); + ps.setInt(1, player.getLocation().getBlockY()-size); + ps.setInt(2, player.getLocation().getBlockY()+size); + ps.setInt(3, player.getLocation().getBlockX()-size); + ps.setInt(4, player.getLocation().getBlockX()+size); + ps.setInt(5, player.getLocation().getBlockZ()-size); + ps.setInt(6, player.getLocation().getBlockZ()+size); rs = ps.executeQuery(); while (rs.next()) { @@ -45,12 +53,12 @@ public class AreaStats implements Runnable ps.close(); ps = conn.prepareStatement("SELECT player, count(player) as num from blocks where replaced > 0 and y > ? and y < ? and x > ? and x < ? and z > ? and z < ? group by player order by count(player) desc limit 10", Statement.RETURN_GENERATED_KEYS); - ps.setInt(1, (int)player.getY()-size); - ps.setInt(2, (int)player.getY()+size); - ps.setInt(3, (int)player.getX()-size); - ps.setInt(4, (int)player.getX()+size); - ps.setInt(5, (int)player.getZ()-size); - ps.setInt(6, (int)player.getZ()+size); + ps.setInt(1, player.getLocation().getBlockY()-size); + ps.setInt(2, player.getLocation().getBlockY()+size); + ps.setInt(3, player.getLocation().getBlockX()-size); + ps.setInt(4, player.getLocation().getBlockX()+size); + ps.setInt(5, player.getLocation().getBlockZ()-size); + ps.setInt(6, player.getLocation().getBlockZ()+size); rs = ps.executeQuery(); while (rs.next()) { @@ -73,14 +81,14 @@ public class AreaStats implements Runnable } } - player.sendMessage(Colors.Blue + "Within " + size + " blocks of you: "); + player.sendMessage("§3Within " + size + " blocks of you: "); if (players.size() == 0) { - player.sendMessage(Colors.Blue + "No results found."); + player.sendMessage("§3No results found."); return; } - player.sendMessage(Colors.Gold + String.format("%-6s %-6s %s", "Creat", "Destr", "Player")); + player.sendMessage("§6" + String.format("%-6s %-6s %s", "Creat", "Destr", "Player")); for (String p: players) { Integer c = created.get(p); @@ -89,7 +97,7 @@ public class AreaStats implements Runnable c = 0; if (d == null) d = 0; - player.sendMessage(Colors.Gold + String.format("%-6d %-6d %s", c, d, p)); + player.sendMessage("§6" + String.format("%-6d %-6d %s", c, d, p)); } } } diff --git a/src/com/bukkit/diddiz/LogBlock/LogBlock.java b/src/com/bukkit/diddiz/LogBlock/LogBlock.java new file mode 100644 index 0000000..8f72eb3 --- /dev/null +++ b/src/com/bukkit/diddiz/LogBlock/LogBlock.java @@ -0,0 +1,634 @@ +package com.bukkit.diddiz.LogBlock; + +import java.io.File; +import java.io.FileWriter; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Type; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockListener; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.block.BlockRightClickEvent; +import org.bukkit.event.player.PlayerChatEvent; +import org.bukkit.event.player.PlayerItemEvent; +import org.bukkit.event.player.PlayerListener; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginLoader; +import org.bukkit.plugin.PluginManager; +import org.bukkit.plugin.java.JavaPlugin; + +import com.bukkit.bootswithdefer.JDCBPool.JDCConnectionDriver; +import com.nijikokun.bukkit.Permissions.Permissions; + +public class LogBlock extends JavaPlugin +{ + private LBLPlayerListener lblPlayerListener = new LBLPlayerListener(); + private LBLBlockListener lblBlockListener = new LBLBlockListener(); + static final Logger log = Logger.getLogger("Minecraft"); + List worlds = getServer().getWorlds(); + private boolean usePermissions = false; + private String dbDriver = "com.mysql.jdbc.Driver"; + private String dbUrl = ""; + private String dbUsername = ""; + private String dbPassword = ""; + private String name = "[LogBlock]"; + private int delay = 10; + private int defaultDist = 20; + private int toolID = 270; + private int toolblockID = 7; + private boolean toolblockRemove = true; + private Consumer consumer = null; + + + private LinkedBlockingQueue bqueue = new LinkedBlockingQueue(); + + public LogBlock(PluginLoader pluginLoader, Server instance, PluginDescriptionFile desc, File folder, File plugin, ClassLoader cLoader) + { + super(pluginLoader, instance, desc, folder, plugin, cLoader); + } + + @Override + public void onEnable() + { + try + { + File file = new File (getDataFolder(), "config.yml"); + if (!file.exists()) + { + file.getParentFile().mkdirs(); + FileWriter writer = new FileWriter(file); + String crlf = System.getProperty("line.separator"); + writer.write("driver : com.mysql.jdbc.Driver" + crlf + + "url : jdbc:mysql://localhost:3306/db" + crlf + + "username : user" + crlf + + "password : pass" + crlf + + "delay : 6" + crlf + + "tool-id : 270" + crlf + + "tool-block-id : 7" + crlf + + "tool-block-remove : true" + crlf + + "default-distance : 20" + crlf + + "usePermissions : false"); + writer.close(); + log.info(name + " Config created"); + } + getConfiguration().load(); + dbDriver = getConfiguration().getString("driver", "com.mysql.jdbc.Driver"); + dbUrl = getConfiguration().getString("url", "jdbc:mysql://localhost:3306/db"); + dbUsername = getConfiguration().getString("username", "user"); + dbPassword = getConfiguration().getString("password", "pass"); + delay = getConfiguration().getInt("delay", 6); + toolID = getConfiguration().getInt("tool-id", 270); + toolblockID = getConfiguration().getInt("tool-block-id", 7); + toolblockRemove = getConfiguration().getBoolean("tool-block-remove", true); + defaultDist = getConfiguration().getInt("default-distance", 20); + if (getConfiguration().getBoolean("usePermissions", false)) + { + if (getServer().getPluginManager().getPlugin("Permissions") != null) + { + usePermissions = true; + log.info(name + " Permissions enabled"); + } + else + log.info(name + " Permissions plugin not found. Use default permissions."); + } + } + catch (Exception e) + { + log.log(Level.SEVERE, name + " Exception while reading config.yml", e); + getServer().getPluginManager().disablePlugin(this); + return; + } + try + { + new JDCConnectionDriver(dbDriver, dbUrl, dbUsername, dbPassword); + } + catch (Exception ex) + { + log.log(Level.SEVERE, name + ": exception while creation database connection pool", ex); + getServer().getPluginManager().disablePlugin(this); + return; + } + if (!checkTables()) + { + log.log(Level.SEVERE, name + " Errors while loading, check logs for more information."); + return; + } + PluginManager pm = getServer().getPluginManager(); + pm.registerEvent(Type.PLAYER_COMMAND, lblPlayerListener, Event.Priority.Normal, this); + pm.registerEvent(Type.BLOCK_RIGHTCLICKED, lblBlockListener, Event.Priority.Monitor, this); + pm.registerEvent(Type.BLOCK_PLACED, lblBlockListener, Event.Priority.Monitor, this); + pm.registerEvent(Type.BLOCK_BREAK, lblBlockListener, Event.Priority.Monitor, this); + pm.registerEvent(Type.PLAYER_ITEM, lblPlayerListener, Event.Priority.Monitor, this); + consumer = new Consumer(); + new Thread(consumer).start(); + log.info(name + " v" + getDescription().getVersion() + " Plugin Enabled."); + } + + @Override + public void onDisable() + { + if (consumer != null) + { + consumer.stop(); + consumer = null; + } + log.info("LogBlock disabled."); + } + + private Connection getConnection() throws SQLException + { + return DriverManager.getConnection("jdbc:jdc:jdcpool"); + } + + private boolean checkTables() + { + Connection conn = null; + ResultSet rs = null; + try { + conn = getConnection(); + DatabaseMetaData dbm = conn.getMetaData(); + rs = dbm.getTables(null, null, "blocks", null); + if (!rs.next()) + { + log.log(Level.SEVERE, name + " blocks table doesn't exist."); + return false; + } + rs = dbm.getTables(null, null, "extra", null); + if (!rs.next()) + { + log.log(Level.SEVERE, name + " extra table doesn't exist."); + return false; + } + return true; + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception", ex); + } finally { + try { + if (rs != null) + rs.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception on close", ex); + } + } + return false; + } + + private void showBlockHistory(Player player, Block b) + { + player.sendMessage("§3Block history (" + b.getX() + ", " + b.getY() + ", " + b.getZ() + "): "); + boolean hist = false; + Connection conn = null; + PreparedStatement ps = null; + ResultSet rs = null; + Timestamp date; + SimpleDateFormat formatter = new SimpleDateFormat("MM-dd hh:mm:ss"); + + try { + conn = getConnection(); + conn.setAutoCommit(false); + ps = conn.prepareStatement("SELECT * from blocks left join extra using (id) where y = ? and x = ? and z = ? order by date desc limit 10", Statement.RETURN_GENERATED_KEYS); + ps.setInt(1, b.getY()); + ps.setInt(2, b.getX()); + ps.setInt(3, b.getZ()); + rs = ps.executeQuery(); + while (rs.next()) + { + date = rs.getTimestamp("date"); + String datestr = formatter.format(date); + String msg = datestr + " " + rs.getString("player") + " "; + if (rs.getInt("type") == 0) + msg = msg + "destroyed " + Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' '); + else if (rs.getInt("replaced") == 0) + { + if (rs.getInt("type") == 323) // sign + msg = msg + "created " + rs.getString("extra"); + else + msg = msg + "created " + Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' '); + } + else + msg = msg + "replaced " + Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' ') + " with " + Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' '); + player.sendMessage("§6" + msg); + hist = true; + } + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception", ex); + } finally { + try { + if (rs != null) + rs.close(); + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception on close", ex); + } + } + if (!hist) + player.sendMessage("§3None."); + } + + private void queueBlock(String playerName, Block block, int typeBefore, int typeAfter) + { + if (block == null || typeBefore < 0 || typeAfter < 0) + return; + BlockRow row = new BlockRow(playerName, typeBefore, typeAfter, block.getX(), block.getY(), block.getZ()); + boolean result = bqueue.offer(row); + if (!result) + log.info(name + " failed to queue block for " + playerName); + } + + //private void queueSign(Player player, Sign sign) + //{ + // int type = 63; + // BlockRow row = new BlockRow(player.getName(), 0, type, sign.getX(), sign.getY(), sign.getZ()); + // String text = "sign"; + // for (int i=0; i < 4; i++) + // text = text + " [" + sign.getLine(i) + "]"; + // row.addExtra(text); + // boolean result = bqueue.offer(row); + // if (!result) + // log.info(name + " failed to queue block for " + player.getName()); + //} + + private boolean CheckPermission(Player player, String permission) + { + if (usePermissions) + return Permissions.Security.permission(player, permission); + else + { + if (permission.equals("logblock.lookup")) + return true; + else if (permission.equals("logblock.area")) + return player.isOp(); + else if (permission.equals("logblock.rollback")) + return player.isOp(); + } + return false; + } + + private int parseTimeSpec(String ts) + { + String[] split = ts.split(" "); + + if (split.length < 2) + return 0; + + int min; + try { + min = Integer.parseInt(split[0]); + } catch (NumberFormatException ex) { + return 0; + } + + if (split[1].startsWith("hour")) + min *= 60; + else if (split[1].startsWith("day")) + min *= (60*24); + return min; + } + + private class LBLPlayerListener extends PlayerListener + { + public void onPlayerCommand(PlayerChatEvent event) { + if (event.isCancelled()) + return; + String[] split = event.getMessage().split(" "); + Player player = event.getPlayer(); + if (split[0].equalsIgnoreCase("/lb")) { + event.setCancelled(true); + if (!CheckPermission(event.getPlayer(),"logblock.area")) + { + event.getPlayer().sendMessage("§cInsufficient permissions"); + return; + } + Connection conn; + try { + conn = getConnection(); + } + catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception", ex); + player.sendMessage("§cError, check server logs."); + return; + } + if (split.length == 1) { + AreaStats th = new AreaStats(conn, player, defaultDist); + new Thread(th).start(); + return; + } + else if (split.length == 2) { + if (split[1].equalsIgnoreCase("world")) { + PlayerWorldStats th = new PlayerWorldStats(conn, player); + new Thread(th).start(); + return; + } + player.sendMessage("§cIncorrect usage."); + return; + } + else if (split.length == 3) { + if (split[1].equalsIgnoreCase("player")) { + PlayerAreaStats th = new PlayerAreaStats(conn, player, split[2], defaultDist); + new Thread(th).start(); + return; + } + else if (split[1].equalsIgnoreCase("area")) { + AreaStats th = new AreaStats(conn, player, Integer.parseInt(split[2])); + new Thread(th).start(); + return; + } + else if (split[1].equalsIgnoreCase("block")) { + int type; + if (Material.matchMaterial(split[2]) != null) + type = Material.matchMaterial(split[2]).getId(); + else + type = Integer.parseInt(split[2]); + AreaBlockSearch th = new AreaBlockSearch(conn, player, type, defaultDist); + new Thread(th).start(); + return; + } + } + player.sendMessage("§cIncorrect usage."); + } + if (split[0].equalsIgnoreCase("/rollback")) + { + event.setCancelled(true); + if (!CheckPermission(event.getPlayer(),"logblock.rollback")) + { + event.getPlayer().sendMessage("§cInsufficient permissions"); + return; + } + int minutes; + String name; + if (split.length < 3) + { + player.sendMessage("§cUsage: /rollback [player] [time spec]"); + return; + } + name = split[1]; + minutes = parseTimeSpec(event.getMessage().substring(event.getMessage().indexOf(' ', 11) + 1)); + + player.sendMessage("§cRolling back " + name + " by " + minutes + " minutes."); + + Connection conn; + try { + conn = getConnection(); + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception", ex); + player.sendMessage("§cError, check server logs."); + return; + } + Rollback rb = new Rollback(conn, name, minutes); + player.sendMessage("§cEdit count: " + rb.count()); + new Thread(rb).start(); + return; + } + } + + public void onPlayerItem(PlayerItemEvent event) + { + if (event.getMaterial() == Material.WATER_BUCKET) + queueBlock(event.getPlayer().getName(), event.getBlockClicked().getFace(event.getBlockFace()), 0, 9); + else if (event.getMaterial() == Material.LAVA_BUCKET) + queueBlock(event.getPlayer().getName(), event.getBlockClicked().getFace(event.getBlockFace()), 0, 11); + } + } + + private class LBLBlockListener extends BlockListener + { + public void onBlockRightClick(BlockRightClickEvent event) + { + if (event.getItemInHand().getTypeId() == toolID && CheckPermission(event.getPlayer(), "logblock.lookup")) + showBlockHistory(event.getPlayer(), event.getBlock()); + } + + public void onBlockPlace(BlockPlaceEvent event) + { + if (event.getItemInHand().getTypeId() == toolblockID && CheckPermission(event.getPlayer(), "logblock.lookup")) + { + showBlockHistory(event.getPlayer(), event.getBlockPlaced()); + if (toolblockRemove) + event.setCancelled(true); + } + else + queueBlock(event.getPlayer().getName(), event.getBlockPlaced(), event.getBlockReplacedState().getTypeId(), event.getBlockPlaced().getTypeId()); + } + + public void onBlockBreak(BlockBreakEvent event) + { + queueBlock(event.getPlayer().getName(), event.getBlock(), event.getBlock().getTypeId(), 0); + } + } + + private class Consumer implements Runnable + { + private boolean stop = false; + Consumer() { stop = false; } + public void stop() { stop = true; } + public void run() + { + PreparedStatement ps = null; + Connection conn = null; + BlockRow b; + + while (!stop) + { + long start = System.currentTimeMillis()/1000L; + int count = 0; + + if (bqueue.size() > 100) + log.info(name + " queue size " + bqueue.size()); + + try { + conn = getConnection(); + conn.setAutoCommit(false); + while (count < 100 && start+delay > (System.currentTimeMillis()/1000L)) + { + b = bqueue.poll(1L, TimeUnit.SECONDS); + + if (b == null) + continue; + ps = conn.prepareStatement("INSERT INTO blocks (date, player, replaced, type, x, y, z) VALUES (now(),?,?,?,?,?,?)", Statement.RETURN_GENERATED_KEYS); + ps.setString(1, b.name); + ps.setInt(2, b.replaced); + ps.setInt(3, b.type); + ps.setInt(4, b.x); + ps.setInt(5, b.y); + ps.setInt(6, b.z); + ps.executeUpdate(); + + if (b.extra != null) + { + ResultSet keys = ps.getGeneratedKeys(); + keys.next(); + int key = keys.getInt(1); + + ps = conn.prepareStatement("INSERT INTO extra (id, extra) values (?,?)"); + ps.setInt(1, key); + ps.setString(2, b.extra); + ps.executeUpdate(); + } + + count++; + } + conn.commit(); + } catch (InterruptedException ex) { + log.log(Level.SEVERE, name + " interrupted exception", ex); + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception", ex); + } finally { + try { + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + log.log(Level.SEVERE, name + " SQL exception on close", ex); + } + } + } + } + } + + private class BlockRow + { + public String name; + public int replaced, type; + public int x, y, z; + public String extra; + + BlockRow(String name, int replaced, int type, int x, int y, int z) + { + this.name = name; + this.replaced = replaced; + this.type = type; + this.x = x; + this.y = y; + this.z = z; + this.extra = null; + } + + //public void addExtra(String extra) + //{ + // this.extra = extra; + //} + + public String toString() + { + return("name: " + name + " before type: " + replaced + " type: " + type + " x: " + x + " y: " + y + " z: " + z); + } + } + + private class Rollback implements Runnable + { + private LinkedBlockingQueue edits = new LinkedBlockingQueue(); + + Rollback(Connection conn, String name, int minutes) + { + String query = "select type, replaced, x, y, z from blocks where player = ? and date > date_sub(now(), interval ? minute) order by date desc"; + PreparedStatement ps = null; + ResultSet rs = null; + edits.clear(); + + try { + conn.setAutoCommit(false); + ps = conn.prepareStatement(query, Statement.RETURN_GENERATED_KEYS); + ps.setString(1, name); + ps.setInt(2, minutes); + rs = ps.executeQuery(); + + while (rs.next()) + { + Edit e = new Edit(rs.getInt("type"), rs.getInt("replaced"), rs.getInt("x"), rs.getInt("y"), rs.getInt("z")); + edits.offer(e); + } + } catch (SQLException ex) { + log.log(Level.SEVERE, this.getClass().getName() + " SQL exception", ex); + } finally { + try { + if (rs != null) + rs.close(); + if (ps != null) + ps.close(); + if (conn != null) + conn.close(); + } catch (SQLException ex) { + log.log(Level.SEVERE, this.getClass().getName() + " SQL exception on close", ex); + } + } + + } + + public int count() + { + return edits.size(); + } + + public void run() + { + Edit e = edits.poll(); + + while (e != null) + { + e.perform(); + e.log(); + e = edits.poll(); + } + } + + private class Edit + { + int type, replaced; + int x, y, z; + + Edit(int type, int replaced, int x, int y, int z) + { + this.type = type; + this.replaced = replaced; + this.x = x; + this.y = y; + this.z = z; + } + + public void perform() + { + Block block = getServer().getWorlds().get(0).getBlockAt(x, y, z); + if (block.getTypeId() == type) + { + if (block.setTypeId(replaced)) + log.info("R (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); + else + log.info("r (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); + } + } + + public void log() + { + int current = getServer().getWorlds().get(0).getBlockTypeIdAt(x, y, z); + if (current == type) + log.info("+ (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); + else + log.info("- (" + x + ", " + y + ", " + z + ") " + replaced + " " + type); + } + } + } +} diff --git a/PlayerAreaStats.java b/src/com/bukkit/diddiz/LogBlock/PlayerAreaStats.java old mode 100755 new mode 100644 similarity index 55% rename from PlayerAreaStats.java rename to src/com/bukkit/diddiz/LogBlock/PlayerAreaStats.java index 1ee1435..ff4b5df --- a/PlayerAreaStats.java +++ b/src/com/bukkit/diddiz/LogBlock/PlayerAreaStats.java @@ -1,8 +1,17 @@ -import java.util.HashSet; -import java.util.HashMap; +package com.bukkit.diddiz.LogBlock; -import java.util.logging.*; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.Material; +import org.bukkit.entity.Player; public class PlayerAreaStats implements Runnable { @@ -32,30 +41,30 @@ public class PlayerAreaStats implements Runnable conn.setAutoCommit(false); ps = conn.prepareStatement("SELECT type, count(type) as num from blocks where type > 0 and player = ? and y > 0 and x > ? and x < ? and z > ? and z < ? group by type order by count(replaced) desc limit 10", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); - ps.setInt(2, (int)player.getX()-size); - ps.setInt(3, (int)player.getX()+size); - ps.setInt(4, (int)player.getZ()-size); - ps.setInt(5, (int)player.getZ()+size); + ps.setInt(2, player.getLocation().getBlockX()-size); + ps.setInt(3, player.getLocation().getBlockX()+size); + ps.setInt(4, player.getLocation().getBlockZ()-size); + ps.setInt(5, player.getLocation().getBlockZ()+size); rs = ps.executeQuery(); while (rs.next()) { - types.add(etc.getDataSource().getItem(rs.getInt("type"))); - created.put(etc.getDataSource().getItem(rs.getInt("type")), rs.getInt("num")); + types.add(Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' ')); + created.put(Material.getMaterial(rs.getInt("type")).toString().toLowerCase().replace('_', ' '), rs.getInt("num")); } rs.close(); ps.close(); ps = conn.prepareStatement("SELECT replaced, count(replaced) as num from blocks where replaced > 0 and player = ? and y > 0 and x > ? and x < ? and z > ? and z < ? group by replaced order by count(replaced) desc limit 10", Statement.RETURN_GENERATED_KEYS); ps.setString(1, name); - ps.setInt(2, (int)player.getX()-size); - ps.setInt(3, (int)player.getX()+size); - ps.setInt(4, (int)player.getZ()-size); - ps.setInt(5, (int)player.getZ()+size); + ps.setInt(2, player.getLocation().getBlockX()-size); + ps.setInt(3, player.getLocation().getBlockX()+size); + ps.setInt(4, player.getLocation().getBlockZ()-size); + ps.setInt(5, player.getLocation().getBlockZ()+size); rs = ps.executeQuery(); while (rs.next()) { - types.add(etc.getDataSource().getItem(rs.getInt("replaced"))); - destroyed.put(etc.getDataSource().getItem(rs.getInt("replaced")), rs.getInt("num")); + types.add(Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' ')); + destroyed.put(Material.getMaterial(rs.getInt("replaced")).toString().toLowerCase().replace('_', ' '), rs.getInt("num")); } } catch (SQLException ex) { @@ -73,14 +82,14 @@ public class PlayerAreaStats implements Runnable } } - player.sendMessage(Colors.Blue + "Player " + name + " within " + size + " blocks of you: "); + player.sendMessage("§3Player " + name + " within " + size + " blocks of you: "); if (types.size() == 0) { - player.sendMessage(Colors.Blue + "No results found."); + player.sendMessage("§3No results found."); return; } - player.sendMessage(Colors.Gold + String.format("%-6s %-6s %s", "Creat", "Destr", "Block")); + player.sendMessage("§6" + String.format("%-6s %-6s %s", "Creat", "Destr", "Block")); for (String t: types) { Integer c = created.get(t); @@ -89,7 +98,7 @@ public class PlayerAreaStats implements Runnable c = 0; if (d == null) d = 0; - player.sendMessage(Colors.Gold + String.format("%-6d %-6d %s", c, d, t)); + player.sendMessage("§6" + String.format("%-6d %-6d %s", c, d, t)); } } } diff --git a/PlayerWorldStats.java b/src/com/bukkit/diddiz/LogBlock/PlayerWorldStats.java old mode 100755 new mode 100644 similarity index 75% rename from PlayerWorldStats.java rename to src/com/bukkit/diddiz/LogBlock/PlayerWorldStats.java index a30a817..716872a --- a/PlayerWorldStats.java +++ b/src/com/bukkit/diddiz/LogBlock/PlayerWorldStats.java @@ -1,8 +1,16 @@ -import java.util.HashSet; -import java.util.HashMap; +package com.bukkit.diddiz.LogBlock; -import java.util.logging.*; -import java.sql.*; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.HashMap; +import java.util.HashSet; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.bukkit.entity.Player; public class PlayerWorldStats implements Runnable { @@ -59,14 +67,14 @@ public class PlayerWorldStats implements Runnable } } - player.sendMessage(Colors.Blue + "Within entire world:"); + player.sendMessage("§3Within entire world:"); if (players.size() == 0) { - player.sendMessage(Colors.Blue + "No results found."); + player.sendMessage("§3No results found."); return; } - player.sendMessage(Colors.Gold + String.format("%-6s %-6s %s", "Creat", "Destr", "Player")); + player.sendMessage("§6" + String.format("%-6s %-6s %s", "Creat", "Destr", "Player")); for (String p: players) { Integer c = created.get(p); @@ -75,7 +83,7 @@ public class PlayerWorldStats implements Runnable c = 0; if (d == null) d = 0; - player.sendMessage(Colors.Gold + String.format("%-6d %-6d %s", c, d, p)); + player.sendMessage("§6" + String.format("%-6d %-6d %s", c, d, p)); } } } diff --git a/src/plugin.yml b/src/plugin.yml new file mode 100644 index 0000000..9e2716d --- /dev/null +++ b/src/plugin.yml @@ -0,0 +1,6 @@ +name: LogBlock +version: 0.1 +author: DiddiZ, bootswithdefer +website: http://www.diddiz.de/minecraft/ +main: com.bukkit.diddiz.LogBlock.LogBlock +description: Logs blocks. \ No newline at end of file