using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Noikoio.RegexBot.ConfigItem;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Noikoio.RegexBot
{
///
/// Configuration loader
///
class Configuration
{
public const string LogPrefix = "Config";
private readonly RegexBot _bot;
private readonly string _configPath;
private DatabaseConfig _dbConfig;
private ServerConfig[] _servers;
// The following values do not change on reload:
private string _botToken;
private string _currentGame;
public string BotUserToken => _botToken;
public string CurrentGame => _currentGame;
public ServerConfig[] Servers => _servers;
public Configuration(RegexBot bot)
{
_bot = bot;
var dsc = Path.DirectorySeparatorChar;
_configPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location)
+ dsc + "settings.json";
}
private async Task LoadFile()
{
var Log = Logger.GetLogger(LogPrefix);
JObject pcfg;
try
{
var ctxt = File.ReadAllText(_configPath);
pcfg = JObject.Parse(ctxt);
return pcfg;
}
catch (Exception ex) when (ex is DirectoryNotFoundException || ex is FileNotFoundException)
{
await Log("Config file not found! Check bot directory for settings.json file.");
return null;
}
catch (UnauthorizedAccessException)
{
await Log("Could not access config file. Check file permissions.");
return null;
}
catch (JsonReaderException jex)
{
await Log("Failed to parse JSON.");
await Log(jex.GetType().Name + " " + jex.Message);
return null;
}
}
///
/// Loads essential, unchanging values needed for bot startup. Returns false on failure.
///
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))
{
log("Error: Bot token not defined. Cannot continue.").Wait();
return false;
}
_currentGame = conf["playing"]?.Value();
// 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;
}
///
/// Reloads the server portion of the configuration file.
///
/// False on failure. Specific reasons will have been sent to log.
public async Task ReloadServerConfig()
{
var config = await LoadFile();
if (config == null) return false;
return await ProcessServerConfig(config);
}
///
/// Converts a json object containing bot configuration into data usable by this program.
/// On success, updates the Servers values and returns true. Returns false on failure.
///
private async Task ProcessServerConfig(JObject conf)
{
var Log = Logger.GetLogger(LogPrefix);
if (!conf["servers"].HasValues)
{
await Log("Error: No server configurations are defined.");
return false;
}
List newservers = new List();
await Log("Reading server configurations...");
foreach (JObject sconf in conf["servers"].Children())
{
// Server name
//if (sconf["id"] == null || sconf["id"].Type != JTokenType.Integer))
if (sconf["id"] == null)
{
await Log("Error: Server ID is missing within definition.");
return false;
}
ulong sid = sconf["id"].Value();
string sname = sconf["name"]?.Value();
var SLog = Logger.GetLogger(LogPrefix + "/" + (sname ?? sid.ToString()));
// Load server moderator list
EntityList mods = new EntityList(sconf["moderators"]);
if (sconf["moderators"] != null) await SLog("Moderator " + mods.ToString());
// Set up module state / load configurations
Dictionary customConfs = new Dictionary();
foreach (var item in _bot.Modules)
{
var confSection = item.Name;
var section = sconf[confSection];
await SLog("Setting up " + item.Name);
object result;
try
{
result = await item.CreateInstanceState(section);
}
catch (RuleImportException ex)
{
await SLog($"{item.Name} failed to load configuration: " + ex.Message);
return false;
}
catch (Exception ex)
{
await SLog("Encountered unhandled exception:");
await SLog(ex.ToString());
return false;
}
customConfs.Add(item, result);
}
// Switch to new configuration
List> rulesfinal = new List>();
newservers.Add(new ServerConfig(sid, mods, new ReadOnlyDictionary(customConfs)));
}
_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();
}
}