From 683b852de7f66879ba0e2d147936e3311313de52 Mon Sep 17 00:00:00 2001 From: Noikoio Date: Fri, 16 Feb 2018 23:41:12 -0800 Subject: [PATCH] Removed public access to database config Database connection is now acquired directly via Configuration instead of an object within it. --- ConfigItem/DatabaseConfig.cs | 44 +++++++++++++------------------- Configuration.cs | 49 +++++++++++++++++++++++++++++++----- EntityCache/CacheUser.cs | 6 ++--- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/ConfigItem/DatabaseConfig.cs b/ConfigItem/DatabaseConfig.cs index 3cee6e3..5afe137 100644 --- a/ConfigItem/DatabaseConfig.cs +++ b/ConfigItem/DatabaseConfig.cs @@ -1,56 +1,43 @@ using Newtonsoft.Json.Linq; using Npgsql; +using System; using System.Threading.Tasks; namespace Noikoio.RegexBot.ConfigItem { class DatabaseConfig { - private readonly bool _enabled; private readonly string _host; private readonly string _user; private readonly string _pass; private readonly string _dbname; - private readonly string _parsemsg; - - /// - /// Gets whether database storage is available. - /// - public bool Available => _enabled; - /// - /// Constructor error message (only if not enabled) - /// - public string ParseMsg => _parsemsg; public DatabaseConfig(JToken ctok) { if (ctok == null || ctok.Type != JTokenType.Object) { - _enabled = false; - _parsemsg = "Database configuration not defined."; - return; + throw new DatabaseConfigLoadException(""); } var conf = (JObject)ctok; _host = conf["hostname"]?.Value() ?? "localhost"; // default to localhost + _user = conf["username"]?.Value(); + if (string.IsNullOrWhiteSpace(_user)) + throw new DatabaseConfigLoadException("Value for username is not defined."); + _pass = conf["password"]?.Value(); + if (string.IsNullOrWhiteSpace(_pass)) + throw new DatabaseConfigLoadException( + $"Value for password is not defined. {nameof(RegexBot)} only supports password authentication."); + _dbname = conf["database"]?.Value(); - - if (string.IsNullOrWhiteSpace(_user) || string.IsNullOrWhiteSpace(_pass) || string.IsNullOrWhiteSpace(_dbname)) - { - _parsemsg = "One or more required values are invalid or not defined."; - _enabled = false; - } - - _parsemsg = null; - _enabled = true; + if (string.IsNullOrWhiteSpace(_dbname)) + throw new DatabaseConfigLoadException("Value for database name is not defined."); } - public async Task GetOpenConnectionAsync() + internal async Task GetOpenConnectionAsync() { - if (!Available) return null; - var cs = new NpgsqlConnectionStringBuilder() { Host = _host, @@ -63,5 +50,10 @@ namespace Noikoio.RegexBot.ConfigItem await db.OpenAsync(); return db; } + + internal class DatabaseConfigLoadException : Exception + { + public DatabaseConfigLoadException(string message) : base(message) { } + } } } diff --git a/Configuration.cs b/Configuration.cs index e49a760..6592ee5 100644 --- a/Configuration.cs +++ b/Configuration.cs @@ -29,7 +29,6 @@ namespace Noikoio.RegexBot public string BotUserToken => _botToken; public string CurrentGame => _currentGame; - public DatabaseConfig Database => _dbConfig; public ServerConfig[] Servers => _servers; @@ -72,22 +71,44 @@ namespace Noikoio.RegexBot /// /// Loads essential, unchanging values needed for bot startup. Returns false on failure. /// - public bool LoadInitialConfig() + internal bool LoadInitialConfig() { var lt = LoadFile(); lt.Wait(); JObject conf = lt.Result; if (conf == null) return false; + var log = Logger.GetLogger(LogPrefix); + _botToken = conf["bot-token"]?.Value(); if (String.IsNullOrWhiteSpace(_botToken)) { - Logger.GetLogger(LogPrefix)("Error: Bot token not defined. Cannot continue.").Wait(); + log("Error: Bot token not defined. Cannot continue.").Wait(); return false; } _currentGame = conf["playing"]?.Value(); - _dbConfig = new DatabaseConfig(conf["database"]); + // Database configuration: + // Either it exists or it doesn't. Read config, but also attempt to make a database connection + // right here, or else make it known that database support is disabled for this instance. + try + { + _dbConfig = new DatabaseConfig(conf["database"]); + var conn = _dbConfig.GetOpenConnectionAsync().GetAwaiter().GetResult(); + conn.Dispose(); + } + catch (DatabaseConfig.DatabaseConfigLoadException ex) + { + if (ex.Message == "") log("Database configuration not found.").Wait(); + else log("Error within database config: " + ex.Message).Wait(); + _dbConfig = null; + } + catch (Npgsql.NpgsqlException ex) + { + log("An error occurred while establishing initial database connection: " + ex.Message).Wait(); + _dbConfig = null; + } + // Modules that will not enable due to lack of database access should say so in their constructors. return true; } @@ -179,7 +200,23 @@ namespace Noikoio.RegexBot _servers = newservers.ToArray(); return true; } - } - + /// + /// Gets a value stating if database access is available. + /// Specifically, indicates if will return a non-null value. + /// + /// + /// Ideally, this value remains constant on runtime. It does not take into account + /// the possibility of the database connection failing during the program's run time. + /// + public bool DatabaseAvailable => _dbConfig != null; + /// + /// Gets an opened connection to the SQL database, if available. + /// + /// + /// An in the opened state, + /// or null if an SQL database is not available. + /// + public Task GetOpenDatabaseConnectionAsync() => _dbConfig?.GetOpenConnectionAsync(); + } } diff --git a/EntityCache/CacheUser.cs b/EntityCache/CacheUser.cs index 0d1f024..740ed9c 100644 --- a/EntityCache/CacheUser.cs +++ b/EntityCache/CacheUser.cs @@ -100,7 +100,7 @@ namespace Noikoio.RegexBot.EntityCache if (lresult != null) return lresult; // Database cache search - var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); if (db == null) return null; // Database not available for query. using (db) return await DbQueryAsync(db, guild, user); } @@ -171,7 +171,7 @@ namespace Noikoio.RegexBot.EntityCache if (lresult.Count() != 0) return lresult; // Database cache search - var db = await RegexBot.Config.Database.GetOpenConnectionAsync(); + var db = await RegexBot.Config.GetOpenDatabaseConnectionAsync(); if (db == null) return null; // Database not available for query. using (db) return await DbQueryAsync(db, guild, name, disc); } @@ -207,7 +207,7 @@ namespace Noikoio.RegexBot.EntityCache { var result = new List(); - using (db = await RegexBot.Config.Database.GetOpenConnectionAsync()) + using (db = await RegexBot.Config.GetOpenDatabaseConnectionAsync()) { using (var c = db.CreateCommand()) {