Removed public access to database config

Database connection is now acquired directly via Configuration
instead of an object within it.
This commit is contained in:
Noikoio 2018-02-16 23:41:12 -08:00
parent e9a4e3e726
commit 0401bfce2d
3 changed files with 64 additions and 35 deletions

View file

@ -1,56 +1,43 @@
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Npgsql; using Npgsql;
using System;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Noikoio.RegexBot.ConfigItem namespace Noikoio.RegexBot.ConfigItem
{ {
class DatabaseConfig class DatabaseConfig
{ {
private readonly bool _enabled;
private readonly string _host; private readonly string _host;
private readonly string _user; private readonly string _user;
private readonly string _pass; private readonly string _pass;
private readonly string _dbname; private readonly string _dbname;
private readonly string _parsemsg;
/// <summary>
/// Gets whether database storage is available.
/// </summary>
public bool Available => _enabled;
/// <summary>
/// Constructor error message (only if not enabled)
/// </summary>
public string ParseMsg => _parsemsg;
public DatabaseConfig(JToken ctok) public DatabaseConfig(JToken ctok)
{ {
if (ctok == null || ctok.Type != JTokenType.Object) if (ctok == null || ctok.Type != JTokenType.Object)
{ {
_enabled = false; throw new DatabaseConfigLoadException("");
_parsemsg = "Database configuration not defined.";
return;
} }
var conf = (JObject)ctok; var conf = (JObject)ctok;
_host = conf["hostname"]?.Value<string>() ?? "localhost"; // default to localhost _host = conf["hostname"]?.Value<string>() ?? "localhost"; // default to localhost
_user = conf["username"]?.Value<string>(); _user = conf["username"]?.Value<string>();
if (string.IsNullOrWhiteSpace(_user))
throw new DatabaseConfigLoadException("Value for username is not defined.");
_pass = conf["password"]?.Value<string>(); _pass = conf["password"]?.Value<string>();
if (string.IsNullOrWhiteSpace(_pass))
throw new DatabaseConfigLoadException(
$"Value for password is not defined. {nameof(RegexBot)} only supports password authentication.");
_dbname = conf["database"]?.Value<string>(); _dbname = conf["database"]?.Value<string>();
if (string.IsNullOrWhiteSpace(_dbname))
if (string.IsNullOrWhiteSpace(_user) || string.IsNullOrWhiteSpace(_pass) || string.IsNullOrWhiteSpace(_dbname)) throw new DatabaseConfigLoadException("Value for database name is not defined.");
{
_parsemsg = "One or more required values are invalid or not defined.";
_enabled = false;
}
_parsemsg = null;
_enabled = true;
} }
public async Task<NpgsqlConnection> GetOpenConnectionAsync() internal async Task<NpgsqlConnection> GetOpenConnectionAsync()
{ {
if (!Available) return null;
var cs = new NpgsqlConnectionStringBuilder() var cs = new NpgsqlConnectionStringBuilder()
{ {
Host = _host, Host = _host,
@ -63,5 +50,10 @@ namespace Noikoio.RegexBot.ConfigItem
await db.OpenAsync(); await db.OpenAsync();
return db; return db;
} }
internal class DatabaseConfigLoadException : Exception
{
public DatabaseConfigLoadException(string message) : base(message) { }
}
} }
} }

View file

@ -29,7 +29,6 @@ namespace Noikoio.RegexBot
public string BotUserToken => _botToken; public string BotUserToken => _botToken;
public string CurrentGame => _currentGame; public string CurrentGame => _currentGame;
public DatabaseConfig Database => _dbConfig;
public ServerConfig[] Servers => _servers; public ServerConfig[] Servers => _servers;
@ -72,22 +71,44 @@ namespace Noikoio.RegexBot
/// <summary> /// <summary>
/// Loads essential, unchanging values needed for bot startup. Returns false on failure. /// Loads essential, unchanging values needed for bot startup. Returns false on failure.
/// </summary> /// </summary>
public bool LoadInitialConfig() internal bool LoadInitialConfig()
{ {
var lt = LoadFile(); var lt = LoadFile();
lt.Wait(); lt.Wait();
JObject conf = lt.Result; JObject conf = lt.Result;
if (conf == null) return false; if (conf == null) return false;
var log = Logger.GetLogger(LogPrefix);
_botToken = conf["bot-token"]?.Value<string>(); _botToken = conf["bot-token"]?.Value<string>();
if (String.IsNullOrWhiteSpace(_botToken)) 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; return false;
} }
_currentGame = conf["playing"]?.Value<string>(); _currentGame = conf["playing"]?.Value<string>();
_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; return true;
} }
@ -179,7 +200,23 @@ namespace Noikoio.RegexBot
_servers = newservers.ToArray(); _servers = newservers.ToArray();
return true; return true;
} }
}
/// <summary>
/// Gets a value stating if database access is available.
/// Specifically, indicates if <see cref="GetOpenDatabaseConnectionAsync"/> will return a non-null value.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
public bool DatabaseAvailable => _dbConfig != null;
/// <summary>
/// Gets an opened connection to the SQL database, if available.
/// </summary>
/// <returns>
/// An <see cref="Npgsql.NpgsqlConnection"/> in the opened state,
/// or null if an SQL database is not available.
/// </returns>
public Task<Npgsql.NpgsqlConnection> GetOpenDatabaseConnectionAsync() => _dbConfig?.GetOpenConnectionAsync();
}
} }

View file

@ -100,7 +100,7 @@ namespace Noikoio.RegexBot.EntityCache
if (lresult != null) return lresult; if (lresult != null) return lresult;
// Database cache search // 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. if (db == null) return null; // Database not available for query.
using (db) return await DbQueryAsync(db, guild, user); using (db) return await DbQueryAsync(db, guild, user);
} }
@ -171,7 +171,7 @@ namespace Noikoio.RegexBot.EntityCache
if (lresult.Count() != 0) return lresult; if (lresult.Count() != 0) return lresult;
// Database cache search // 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. if (db == null) return null; // Database not available for query.
using (db) return await DbQueryAsync(db, guild, name, disc); using (db) return await DbQueryAsync(db, guild, name, disc);
} }
@ -207,7 +207,7 @@ namespace Noikoio.RegexBot.EntityCache
{ {
var result = new List<CacheUser>(); var result = new List<CacheUser>();
using (db = await RegexBot.Config.Database.GetOpenConnectionAsync()) using (db = await RegexBot.Config.GetOpenDatabaseConnectionAsync())
{ {
using (var c = db.CreateCommand()) using (var c = db.CreateCommand())
{ {