diff --git a/src/commands/CommandBoop.java b/src/commands/CommandBoop.java index ccd0a41..6774212 100644 --- a/src/commands/CommandBoop.java +++ b/src/commands/CommandBoop.java @@ -56,7 +56,7 @@ public class CommandBoop extends Command { super(level, rating); boopTracker = new BoopTracker(); - DatabaseManager.instance.Register(boopTracker); + DatabaseManager.instance.globalRegister(boopTracker); } @Override diff --git a/src/commands/CommandDBFlush.java b/src/commands/CommandDBFlush.java index 484ac61..d0f37b8 100644 --- a/src/commands/CommandDBFlush.java +++ b/src/commands/CommandDBFlush.java @@ -24,7 +24,7 @@ public class CommandDBFlush extends Command @Override public void OnRun(KittyGuild guild, KittyChannel channel, KittyUser user, UserInput input, Response res) { - int numUpdated = DatabaseManager.instance.Upkeep(); + int numUpdated = DatabaseManager.instance.upkeep(); KittyEmbed embed = new KittyEmbed(); embed.title = "Database queue flushed"; diff --git a/src/commands/CommandDBStats.java b/src/commands/CommandDBStats.java index ae8c012..033bf34 100644 --- a/src/commands/CommandDBStats.java +++ b/src/commands/CommandDBStats.java @@ -33,9 +33,9 @@ public class CommandDBStats extends Command { KittyEmbed embed = new KittyEmbed(); embed.title = "Database Info"; - embed.descriptionText = "**Tracked Items:** " + DatabaseManager.instance.GetTrackedObjectsSize(); + embed.descriptionText = "**Tracked Items:** " + DatabaseManager.instance.getTrackedObjectsSize(); embed.descriptionText += "\n"; - embed.descriptionText += "**Last Upkeep:** " + dateFormat.format(DatabaseManager.instance.GetLastUpkeep()) + " UTC-7"; + embed.descriptionText += "**Last Upkeep:** " + dateFormat.format(DatabaseManager.instance.getLastUpkeep()) + " UTC-7"; embed.color = new Color(7*16, 8*16, 9*16); res.CallEmbed(embed); diff --git a/src/commands/CommandShutdown.java b/src/commands/CommandShutdown.java index 06eabda..57538a6 100644 --- a/src/commands/CommandShutdown.java +++ b/src/commands/CommandShutdown.java @@ -47,7 +47,7 @@ public class CommandShutdown extends Command { // Force upkeep, this works so long as upkeep is on the main thread. res.CallImmediate(LocStrings.Stub("ShutdownSafe")); - DatabaseManager.instance.Upkeep(); + DatabaseManager.instance.upkeep(); GlobalLog.Warn(LogFilter.Command, LocStrings.Lookup("ShutdownSafe")); System.exit(0); diff --git a/src/core/DatabaseDriver.java b/src/core/DatabaseDriverKeyValue.java similarity index 76% rename from src/core/DatabaseDriver.java rename to src/core/DatabaseDriverKeyValue.java index b751e32..df8032e 100644 --- a/src/core/DatabaseDriver.java +++ b/src/core/DatabaseDriverKeyValue.java @@ -13,19 +13,21 @@ import utils.LogFilter; // This is where we put everything and swap out the moving parts, // ie MySQL, PostgreSQL, SQLite, etc... // -// Right now this can be messed up with SQL injection stuff if users -// are allowed to directly touch data. Leaving it like this temporarily, -// and is for prototyping only! -public class DatabaseDriver +// This isn't overly flexible though, +public class DatabaseDriverKeyValue { // Config and local varaibles private JDBCDriver driver; - private final String globalTableName = "kitty_globals"; - private final String globalKeyName = "GlobalKey"; - private final String globalValueName = "GlobalValue"; + private final String tableName; // Table name + private final String keyColumnName; // Column name for the key + private final String valueColumnName; // Column name for the value - public DatabaseDriver() + public DatabaseDriverKeyValue(String tableName, String keyColumnName, String valueColumnName) { + this.tableName = tableName; + this.keyColumnName = keyColumnName; + this.valueColumnName = valueColumnName; + driver = null; } @@ -47,7 +49,7 @@ public class DatabaseDriver } // Verify tables we want to use exist - EnsureTableExists(globalTableName, globalKeyName, globalValueName); // General table. Do not remove. + EnsureTableExists(tableName, keyColumnName, valueColumnName); // General table. Do not remove. return true; } @@ -87,7 +89,7 @@ public class DatabaseDriver // Prototype formatting for key updating private void UpdateKey(String key, String value) { - String command = "UPDATE " + globalTableName + " SET " + globalValueName + " = ? WHERE " + globalKeyName + " = ?"; + String command = "UPDATE " + tableName + " SET " + valueColumnName + " = ? WHERE " + keyColumnName + " = ?"; boolean status = driver.ExecuteStatement(JDBCStatementType.Update, command, new String[] { value, key }); GlobalLog.Log(LogFilter.Database, "UpdateKey status: " + status); } @@ -95,7 +97,7 @@ public class DatabaseDriver // Protoype updating for seeing if a key exists private boolean HasKey(String key) { - String command = "SELECT COUNT(1) as count FROM " + globalTableName + " WHERE " + globalKeyName + " = ?"; + String command = "SELECT COUNT(1) as count FROM " + tableName + " WHERE " + keyColumnName + " = ?"; ResultSet set = driver.ExecuteReturningStatement(JDBCStatementType.Select, command, new String[] { key }); String out = ResultAsString(set, "count"); return out.charAt(0) == '1'; @@ -104,7 +106,7 @@ public class DatabaseDriver // Prototype for getting a key private String GetKey(String key) { - String command = "SELECT " + globalValueName + " as searchedKey FROM " + globalTableName +" WHERE " + globalKeyName + " = ?"; + String command = "SELECT " + valueColumnName + " as searchedKey FROM " + tableName +" WHERE " + keyColumnName + " = ?"; ResultSet set = driver.ExecuteReturningStatement(JDBCStatementType.Select, command, new String[] { key }); return ResultAsString(set, "searchedKey"); } @@ -112,7 +114,7 @@ public class DatabaseDriver // Prototype for creating a key private void CreateKey(String key, String value) { - String command = "INSERT INTO " + globalTableName + " (GlobalKey, GlobalValue) VALUES (?, ?)"; + String command = "INSERT INTO " + tableName + " (GlobalKey, GlobalValue) VALUES (?, ?)"; boolean status = driver.ExecuteStatement(JDBCStatementType.Insert, command, new String[] { key, value }); GlobalLog.Log(LogFilter.Database, "CreateKey status: " + status); } diff --git a/src/core/DatabaseManager.java b/src/core/DatabaseManager.java index a8640a6..c4a12c1 100644 --- a/src/core/DatabaseManager.java +++ b/src/core/DatabaseManager.java @@ -5,16 +5,33 @@ import java.util.Vector; import utils.GlobalLog; import utils.LogFilter; +// This wrangles all of the differentiated storage systems. Internally, these may be tables, +// different databases, etc -- the goal is that it's abstracted away. +// +// TODO: Implement intermediate structure on account of duplicate patterns showing up. public class DatabaseManager { // Singleton accessor public static DatabaseManager instance = null; + // Database names and configs + private final String globalTableName = "kitty_globals"; // Table name + private final String globalKeyColumnName = "GlobalKey"; // Column name for the key + private final String globalValueColumnName = "GlobalValue"; // Column name for the value + + private final String characterTableName = "kitty_characters"; // Table name + private final String characterKeyColumnName = "CharacterKey"; // Column name for the key + private final String characterValueColumnName = "CharacterValue"; // Column name for the value + + // Private internal variables - private Vector trackedObjects; - private DatabaseDriver driver; + private Vector globalDataTrackedObjects; + private Vector characterDataTrackedObjects; + private DatabaseDriverKeyValue globalDataDriver; + private DatabaseDriverKeyValue characterDataDriver; private Date lastUpkeep; + // Constructor to enforce singleton. public DatabaseManager() { GlobalLog.Log(LogFilter.Database, "Creating database manager"); @@ -29,75 +46,157 @@ public class DatabaseManager return; } + // Initialize variables lastUpkeep = new Date(); - trackedObjects = new Vector(); - driver = new DatabaseDriver(); + globalDataTrackedObjects = new Vector(); + characterDataTrackedObjects = new Vector(); - if(driver.Connect() == false) + // Initialize data sets + globalDataDriver = new DatabaseDriverKeyValue(globalTableName, globalKeyColumnName, globalValueColumnName); + characterDataDriver = new DatabaseDriverKeyValue(characterTableName, characterKeyColumnName, characterValueColumnName); + + // Connect data sets + if(globalDataDriver.Connect() == false) { - GlobalLog.Error("A database failed to connect. Currently, without the DB, this bot can not run."); + GlobalLog.Error("Global database failed to connect. Without this DB, this bot can not run."); + System.exit(1); + } + + if(characterDataDriver.Connect() == false) + { + GlobalLog.Error("Character database failed to connect. Without this DB, this bot can not run."); System.exit(1); } } // Thumbs through registered objects and syncs them with the database. // Consider moving this operation to a separate thread. - public int Upkeep() + public int upkeep() { - synchronized(trackedObjects) + int total = 0; + + total += globalDataUpkeep(); + total += characterDataUpkeep(); + + lastUpkeep = new Date(); + return total; + } + + // Global data management and updating, returns how many items were updated. + private int globalDataUpkeep() + { + synchronized(globalDataTrackedObjects) { int numUpdated = 0; - for(int i = 0 ; i < trackedObjects.size(); ++i) + for(int i = 0; i < globalDataTrackedObjects.size(); ++i) { - DatabaseTrackedObject dto = trackedObjects.get(i); + DatabaseTrackedObject dto = globalDataTrackedObjects.get(i); if(dto.IsDirty()) { - SetRemoteValue(dto.identifier, dto.Serialize()); + globalSetRemoteValue(dto.identifier, dto.Serialize()); dto.Resolve(); ++numUpdated; } } - lastUpkeep = new Date(); + return numUpdated; + } + } + + // Character data management and updating, returns how many items were updated. + private int characterDataUpkeep() + { + synchronized(characterDataTrackedObjects) + { + int numUpdated = 0; + + for(int i = 0; i < characterDataTrackedObjects.size(); ++i) + { + DatabaseTrackedObject dto = characterDataTrackedObjects.get(i); + + if(dto.IsDirty()) + { + characterSetRemoteValue(dto.identifier, dto.Serialize()); + dto.Resolve(); + ++numUpdated; + } + } return numUpdated; } } - public void Register(DatabaseTrackedObject tracked) + + ///////////////// + // Global Data // + ///////////////// + public void globalRegister(DatabaseTrackedObject tracked) { - synchronized(trackedObjects) + synchronized(globalDataTrackedObjects) { - trackedObjects.add(tracked); - tracked.DeSerialzie(GetRemoteValue(tracked.identifier)); + globalDataTrackedObjects.add(tracked); + tracked.DeSerialzie(globalGetRemoteValue(tracked.identifier)); } } - public String GetRemoteValue(String key) + private String globalGetRemoteValue(String key) { - synchronized(driver) + synchronized(globalDataDriver) { - return driver.CreateGetKey(key); + return globalDataDriver.CreateGetKey(key); } } - public void SetRemoteValue(String key, String value) + private void globalSetRemoteValue(String key, String value) { - synchronized(driver) + synchronized(globalDataDriver) { - driver.CreateSetKey(key, value); + globalDataDriver.CreateSetKey(key, value); } } - public Date GetLastUpkeep() + + //////////////////// + // Character Data // + //////////////////// + public void characterRegister(DatabaseTrackedObject tracked) + { + synchronized(characterDataTrackedObjects) + { + characterDataTrackedObjects.add(tracked); + tracked.DeSerialzie(characterGetRemoteValue(tracked.identifier)); + } + } + + private String characterGetRemoteValue(String key) + { + synchronized(characterDataDriver) + { + return characterDataDriver.CreateGetKey(key); + } + } + + private void characterSetRemoteValue(String key, String value) + { + synchronized(characterDataDriver) + { + characterDataDriver.CreateSetKey(key, value); + } + } + + + /////////////////////// + // Utility functions // + /////////////////////// + public Date getLastUpkeep() { return lastUpkeep; } - public int GetTrackedObjectsSize() + public int getTrackedObjectsSize() { - return trackedObjects.size(); + return globalDataTrackedObjects.size(); } } diff --git a/src/core/ObjectBuilderFactory.java b/src/core/ObjectBuilderFactory.java index d5fe6f0..ec55748 100644 --- a/src/core/ObjectBuilderFactory.java +++ b/src/core/ObjectBuilderFactory.java @@ -131,7 +131,7 @@ public class ObjectBuilderFactory { // Construct a new guild with defaults guild = new KittyGuild(uid, new AdminControl(event.getGuild()), emotesString); - DatabaseManager.instance.Register(guild); + DatabaseManager.instance.globalRegister(guild); guildCache.put(uid, guild); } } @@ -241,7 +241,7 @@ public class ObjectBuilderFactory String discordID = event.getMember().getUser().getId(); String avatarID = event.getAuthor().getAvatarUrl(); user = new KittyUser(name, guild, role, uid, avatarID, discordID); - DatabaseManager.instance.Register(user); + DatabaseManager.instance.globalRegister(user); userCache.put(uid, user); } } @@ -285,7 +285,7 @@ public class ObjectBuilderFactory KittyRole role = KittyRole.General; KittyGuild guild = guildCache.get(guildID); user = new KittyUser(name, guild, role, uid, avatarID, discordID); - DatabaseManager.instance.Register(user); + DatabaseManager.instance.globalRegister(user); userCache.put(uid, user); } } diff --git a/src/dataStructures/KittyGuild.java b/src/dataStructures/KittyGuild.java index 7331f01..b2d6868 100644 --- a/src/dataStructures/KittyGuild.java +++ b/src/dataStructures/KittyGuild.java @@ -35,8 +35,8 @@ public class KittyGuild extends DatabaseTrackedObject private void RegisterTrackedObjects() { - DatabaseManager.instance.Register(roleList); - DatabaseManager.instance.Register(beans); + DatabaseManager.instance.globalRegister(roleList); + DatabaseManager.instance.globalRegister(beans); } // Default content for a guild diff --git a/src/main/Superintendent.java b/src/main/Superintendent.java index 207a642..b7b19f4 100644 --- a/src/main/Superintendent.java +++ b/src/main/Superintendent.java @@ -92,7 +92,7 @@ public class Superintendent // Upkeep database lazily on occasion if(delayTimerCurrent.decrementAndGet() < 0) { - databaseManager.Upkeep(); + databaseManager.upkeep(); delayTimerCurrent.set(delayTimerReset); } diff --git a/src/network/JDBCDriverSQLite.java b/src/network/JDBCDriverSQLite.java index 8fc64ee..594906f 100644 --- a/src/network/JDBCDriverSQLite.java +++ b/src/network/JDBCDriverSQLite.java @@ -13,34 +13,41 @@ import utils.LogFilter; // Only to be called by the generic driver. public class JDBCDriverSQLite extends JDBCDriver { - Connection connection = null; - String databaseFolder = "db/"; - String databaseName = "catfood"; + private static Connection connection = null; + public static final String databaseFolder = "db/"; + public static final String databaseName = "catfood"; @Override public boolean Connect() - { - try + { + if(connection == null) { - { // Scope to discard file... - File f = new File(databaseFolder); - if(!f.exists() || !f.isDirectory()) - { - f.mkdir(); + try + { + { // Scope to discard file... + File f = new File(databaseFolder); + if(!f.exists() || !f.isDirectory()) + { + f.mkdir(); + } } + + // db parameters + String url = "jdbc:sqlite:" + databaseFolder + databaseName + ".db"; + + // create a connection to the database + connection = DriverManager.getConnection(url); + + GlobalLog.Log(LogFilter.Database, "Connection to SQLite has been established."); + } + catch (SQLException e) + { + GlobalLog.Error(e.getMessage()); } - - // db parameters - String url = "jdbc:sqlite:db/" + databaseName + ".db"; - - // create a connection to the database - connection = DriverManager.getConnection(url); - - GlobalLog.Log(LogFilter.Database, "Connection to SQLite has been established."); - } - catch (SQLException e) + } + else { - GlobalLog.Error(e.getMessage()); + GlobalLog.Log(LogFilter.Database, "SQLite database asked to connect a second time. This is perfectly fine as this app has all its data tied together."); } return connection != null; @@ -52,7 +59,9 @@ public class JDBCDriverSQLite extends JDBCDriver try { if (connection != null) + { connection.close(); + } return true; }