diff --git a/ProjectLighthouse.Tests/DatabaseFactAttribute.cs b/ProjectLighthouse.Tests/DatabaseFactAttribute.cs index 5f34d624..4bd92a87 100644 --- a/ProjectLighthouse.Tests/DatabaseFactAttribute.cs +++ b/ProjectLighthouse.Tests/DatabaseFactAttribute.cs @@ -10,13 +10,10 @@ public sealed class DatabaseFactAttribute : FactAttribute public DatabaseFactAttribute() { - ServerConfiguration.Instance = new ServerConfiguration - { - DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse", - }; + ServerConfiguration.Instance.DbConnectionString = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; if (!ServerStatics.DbConnected) this.Skip = "Database not available"; else - lock(migrateLock) + lock (migrateLock) { using Database database = new(); database.Database.Migrate(); diff --git a/ProjectLighthouse/Configuration/ConfigurationBase.cs b/ProjectLighthouse/Configuration/ConfigurationBase.cs new file mode 100644 index 00000000..156fa4e6 --- /dev/null +++ b/ProjectLighthouse/Configuration/ConfigurationBase.cs @@ -0,0 +1,231 @@ +#nullable enable +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using LBPUnion.ProjectLighthouse.Logging; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +// ReSharper disable VirtualMemberCallInConstructor +// ReSharper disable StaticMemberInGenericType + +namespace LBPUnion.ProjectLighthouse.Configuration; + +[Serializable] +public abstract class ConfigurationBase where T : class, new() +{ + private static readonly Lazy sInstance = new(CreateInstanceOfT); + + public static T Instance => sInstance.Value; + + // ReSharper disable once UnusedMember.Global + // Used with reflection to determine if the config was successfully loaded + public static bool IsConfigured => sInstance.IsValueCreated; + + // Used to prevent an infinite loop of the config trying to load itself when deserializing + // This is intentionally not released in the constructor + private static readonly SemaphoreSlim constructorLock = new(1, 1); + + // Global mutex for synchronizing processes so that only one process can read a config at a time + // Mostly useful for migrations so only one server will try to rewrite the config file + private static Mutex? _configFileMutex; + + [YamlIgnore] + public abstract string ConfigName { get; set; } + + [YamlMember(Alias = "configVersionDoNotModifyOrYouWillBeSlapped", Order = -2)] + public abstract int ConfigVersion { get; set; } + + [YamlIgnore] + // Used to indicate whether the config will be generated with a .configme extension or not + public virtual bool NeedsConfiguration { get; set; } = true; + + [YamlMember(Order = -1)] + public bool ConfigReloading { get; set; } = false; + + // Used to listen for changes to the config file + private static FileSystemWatcher? _fileWatcher; + + internal ConfigurationBase() + { + // Deserializing this class will call this constructor and we don't want it to actually load the config + // So each subsequent time this constructor is called we want to exit early + if (constructorLock.CurrentCount == 0) + { + return; + } + + constructorLock.Wait(); + if (ServerStatics.IsUnitTesting) + return; // Unit testing, we don't want to read configurations here since the tests will provide their own + + // Trim ConfigName by 4 to remove the .yml + string mutexName = $"Global\\LighthouseConfig-{this.ConfigName[..^4]}"; + + _configFileMutex = new Mutex(false, mutexName); + + this.loadStoredConfig(); + + if (!this.ConfigReloading) return; + + _fileWatcher = new FileSystemWatcher + { + Path = Environment.CurrentDirectory, + Filter = this.ConfigName, + NotifyFilter = NotifyFilters.LastWrite, // only watch for writes to config file + }; + + _fileWatcher.Changed += this.onConfigChanged; // add event handler + + _fileWatcher.EnableRaisingEvents = true; // begin watching + } + + internal void onConfigChanged(object sender, FileSystemEventArgs e) + { + if (_fileWatcher == null) return; + try + { + _fileWatcher.EnableRaisingEvents = false; + Debug.Assert(e.Name == this.ConfigName); + Logger.Info("Configuration file modified, reloading config...", LogArea.Config); + Logger.Warn("Some changes may not apply; they will require a restart of Lighthouse.", LogArea.Config); + + this.loadStoredConfig(); + + Logger.Success("Successfully reloaded the configuration!", LogArea.Config); + } + finally + { + _fileWatcher.EnableRaisingEvents = true; + } + } + + private void loadStoredConfig() + { + try + { + _configFileMutex?.WaitOne(); + + ConfigurationBase? storedConfig; + + if (File.Exists(this.ConfigName) && (storedConfig = this.fromFile(this.ConfigName)) != null) + { + if (storedConfig.ConfigVersion < GetVersion()) + { + int newVersion = GetVersion(); + Logger.Info($"Upgrading config file from version {storedConfig.ConfigVersion} to version {newVersion}", LogArea.Config); + storedConfig.writeConfig(this.ConfigName + ".bak"); + this.loadConfig(storedConfig); + this.ConfigVersion = newVersion; + this.writeConfig(this.ConfigName); + } + else + { + this.loadConfig(storedConfig); + } + } + else if (!File.Exists(this.ConfigName)) + { + if (this.NeedsConfiguration) + { + Logger.Warn("The configuration file was not found. " + + "A blank configuration file has been created for you at " + + $"{Path.Combine(Environment.CurrentDirectory, this.ConfigName + ".configme")}", + LogArea.Config); + this.writeConfig(this.ConfigName + ".configme"); + this.ConfigVersion = -1; + } + else + { + this.writeConfig(this.ConfigName); + } + } + } + finally + { + _configFileMutex?.ReleaseMutex(); + } + } + + /// + /// Uses reflection to set all values of this class to the values of another class + /// + /// The config to be loaded + private void loadConfig(ConfigurationBase otherConfig) + { + foreach (PropertyInfo propertyInfo in otherConfig.GetType().GetProperties()) + { + object? value = propertyInfo.GetValue(otherConfig); + PropertyInfo? local = this.GetType().GetProperty(propertyInfo.Name); + if (value == null || local == null || Attribute.IsDefined(local, typeof(YamlIgnoreAttribute))) + { + continue; + } + + local.SetValue(this, value); + } + } + + private ConfigurationBase? fromFile(string path) + { + IDeserializer deserializer = new DeserializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance) + .IgnoreUnmatchedProperties() + .Build(); + + try + { + string text = File.ReadAllText(path); + + if (text.StartsWith("configVersionDoNotModifyOrYouWillBeSlapped")) + return this.Deserialize(deserializer, text); + } + catch (Exception e) + { + Logger.Error($"Error while deserializing config: {e}", LogArea.Config); + return null; + } + + Logger.Error($"Unable to load config for {this.GetType().Name}", LogArea.Config); + return null; + } + + public abstract ConfigurationBase Deserialize(IDeserializer deserializer, string text); + + private string serializeConfig() => new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).Build().Serialize(this); + + private void writeConfig(string path) => File.WriteAllText(path, this.serializeConfig()); + + public void Dispose() + { + _configFileMutex?.Dispose(); + _fileWatcher?.Dispose(); + constructorLock.Dispose(); + } + + public static int GetVersion() => version.Value; + + private static readonly Lazy version = new(fetchVersion); + + // Obtain a fresh version of the class to get the coded config version + private static int fetchVersion() + { + object instance = CreateInstanceOfT(); + int? ver = instance.GetType().GetProperty("ConfigVersion")?.GetValue(instance) as int?; + return ver.GetValueOrDefault(); + } + + private static T CreateInstanceOfT() + { + try + { + return Activator.CreateInstance(typeof(T), true) as T ?? throw new InvalidOperationException(); + } + catch (Exception e) + { + Logger.Error($"Failed to create instance of {typeof(T).Name}: {e}", LogArea.Config); + return new T(); + } + } +} \ No newline at end of file diff --git a/ProjectLighthouse/Configuration/Legacy/LegacyServerSettings.cs b/ProjectLighthouse/Configuration/Legacy/LegacyServerSettings.cs deleted file mode 100644 index e1410b32..00000000 --- a/ProjectLighthouse/Configuration/Legacy/LegacyServerSettings.cs +++ /dev/null @@ -1,213 +0,0 @@ -using System.IO; -using System.Text.Json; -using LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories; -using LBPUnion.ProjectLighthouse.Types; - -namespace LBPUnion.ProjectLighthouse.Configuration.Legacy; -#nullable enable - -internal class LegacyServerSettings -{ - - #region Meta - - public const string ConfigFileName = "lighthouse.config.json"; - - #endregion - - #region InfluxDB - - public bool InfluxEnabled { get; set; } - public bool InfluxLoggingEnabled { get; set; } - public string InfluxOrg { get; set; } = "lighthouse"; - public string InfluxBucket { get; set; } = "lighthouse"; - public string InfluxToken { get; set; } = ""; - public string InfluxUrl { get; set; } = "http://localhost:8086"; - - #endregion - - public string EulaText { get; set; } = ""; - - #if !DEBUG - public string AnnounceText { get; set; } = "You are now logged in as %user."; - #else - public string AnnounceText { get; set; } = "You are now logged in as %user (id: %id)."; - #endif - - public string DbConnectionString { get; set; } = "server=127.0.0.1;uid=root;pwd=lighthouse;database=lighthouse"; - - public string ExternalUrl { get; set; } = "http://localhost:10060"; - public string ServerDigestKey { get; set; } = ""; - public string AlternateDigestKey { get; set; } = ""; - public bool UseExternalAuth { get; set; } - - public bool CheckForUnsafeFiles { get; set; } = true; - - public bool RegistrationEnabled { get; set; } = true; - - #region UGC Limits - - /// - /// The maximum amount of slots allowed on users' earth - /// - public int EntitledSlots { get; set; } = 50; - - public int ListsQuota { get; set; } = 50; - - public int PhotosQuota { get; set; } = 500; - - public bool ProfileCommentsEnabled { get; set; } = true; - - public bool LevelCommentsEnabled { get; set; } = true; - - public bool LevelReviewsEnabled { get; set; } = true; - - #endregion - - #region Google Analytics - - public bool GoogleAnalyticsEnabled { get; set; } - - public string GoogleAnalyticsId { get; set; } = ""; - - #endregion - - public bool BlockDeniedUsers { get; set; } = true; - - public bool BooingEnabled { get; set; } = true; - - public FilterMode UserInputFilterMode { get; set; } = FilterMode.None; - - #region Discord Webhook - - public bool DiscordWebhookEnabled { get; set; } - - public string DiscordWebhookUrl { get; set; } = ""; - - #endregion - - public bool ConfigReloading { get; set; } = true; - - public string MissingIconHash { get; set; } = ""; - - #region HCaptcha - - public bool HCaptchaEnabled { get; set; } - - public string HCaptchaSiteKey { get; set; } = ""; - - public string HCaptchaSecret { get; set; } = ""; - - #endregion - - public string ServerListenUrl { get; set; } = "http://localhost:10060"; - - public bool ConvertAssetsOnStartup { get; set; } = true; - - #region SMTP - - public bool SMTPEnabled { get; set; } - - public string SMTPHost { get; set; } = ""; - - public int SMTPPort { get; set; } = 587; - - public string SMTPFromAddress { get; set; } = "lighthouse@example.com"; - - public string SMTPFromName { get; set; } = "Project Lighthouse"; - - public string SMTPPassword { get; set; } = ""; - - public bool SMTPSsl { get; set; } = true; - - #endregion - - internal static LegacyServerSettings? FromFile(string path) - { - string data = File.ReadAllText(path); - return JsonSerializer.Deserialize(data); - } - - internal ServerConfiguration ToNewConfiguration() - { - ServerConfiguration configuration = new(); - configuration.ConfigReloading = this.ConfigReloading; - configuration.AnnounceText = this.AnnounceText; - configuration.EulaText = this.EulaText; - configuration.ExternalUrl = this.ExternalUrl; - configuration.DbConnectionString = this.DbConnectionString; - configuration.CheckForUnsafeFiles = this.CheckForUnsafeFiles; - configuration.UserInputFilterMode = this.UserInputFilterMode; - - // configuration categories - configuration.InfluxDB = new InfluxDBConfiguration - { - InfluxEnabled = this.InfluxEnabled, - LoggingEnabled = this.InfluxLoggingEnabled, - Bucket = this.InfluxBucket, - Organization = this.InfluxOrg, - Token = this.InfluxToken, - Url = this.InfluxUrl, - }; - - configuration.Authentication = new AuthenticationConfiguration - { - RegistrationEnabled = this.RegistrationEnabled, - }; - - configuration.Captcha = new CaptchaConfiguration - { - CaptchaEnabled = this.HCaptchaEnabled, - SiteKey = this.HCaptchaSiteKey, - Secret = this.HCaptchaSecret, - }; - - configuration.Mail = new MailConfiguration - { - MailEnabled = this.SMTPEnabled, - Host = this.SMTPHost, - Password = this.SMTPPassword, - Port = this.SMTPPort, - FromAddress = this.SMTPFromAddress, - FromName = this.SMTPFromName, - UseSSL = this.SMTPSsl, - }; - - configuration.DigestKey = new DigestKeyConfiguration - { - PrimaryDigestKey = this.ServerDigestKey, - AlternateDigestKey = this.AlternateDigestKey, - }; - - configuration.DiscordIntegration = new DiscordIntegrationConfiguration - { - DiscordIntegrationEnabled = this.DiscordWebhookEnabled, - Url = this.DiscordWebhookUrl, - }; - - configuration.GoogleAnalytics = new GoogleAnalyticsConfiguration - { - AnalyticsEnabled = this.GoogleAnalyticsEnabled, - Id = this.GoogleAnalyticsId, - }; - - configuration.UserGeneratedContentLimits = new UserGeneratedContentLimitConfiguration - { - BooingEnabled = this.BooingEnabled, - EntitledSlots = this.EntitledSlots, - ListsQuota = this.ListsQuota, - PhotosQuota = this.PhotosQuota, - LevelCommentsEnabled = this.LevelCommentsEnabled, - LevelReviewsEnabled = this.LevelReviewsEnabled, - ProfileCommentsEnabled = this.ProfileCommentsEnabled, - }; - - configuration.WebsiteConfiguration = new WebsiteConfiguration - { - MissingIconHash = this.MissingIconHash, - ConvertAssetsOnStartup = this.ConvertAssetsOnStartup, - }; - - return configuration; - } -} \ No newline at end of file diff --git a/ProjectLighthouse/Configuration/ServerConfiguration.cs b/ProjectLighthouse/Configuration/ServerConfiguration.cs index f51bd854..bc9bdec1 100644 --- a/ProjectLighthouse/Configuration/ServerConfiguration.cs +++ b/ProjectLighthouse/Configuration/ServerConfiguration.cs @@ -1,174 +1,20 @@ -#nullable enable -using System; -using System.Diagnostics; -using System.IO; -using LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories; -using LBPUnion.ProjectLighthouse.Configuration.Legacy; -using LBPUnion.ProjectLighthouse.Logging; +using LBPUnion.ProjectLighthouse.Configuration.ConfigurationCategories; using LBPUnion.ProjectLighthouse.Types; using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; namespace LBPUnion.ProjectLighthouse.Configuration; -[Serializable] -public class ServerConfiguration +public class ServerConfiguration : ConfigurationBase { // HEY, YOU! // THIS VALUE MUST BE INCREMENTED FOR EVERY CONFIG CHANGE! // // This is so Lighthouse can properly identify outdated configurations and update them with newer settings accordingly. - // If you are modifying anything here that isn't outside of a method, this value MUST be incremented. - // It is also strongly recommended to not remove any items, else it will cause deserialization errors. - // You can use an ObsoleteAttribute instead. Make sure you set it to error, though. - // + // If you are modifying anything here, this value MUST be incremented. // Thanks for listening~ - public const int CurrentConfigVersion = 16; - - #region Meta - - public static ServerConfiguration Instance; - - [YamlMember(Alias = "configVersionDoNotModifyOrYouWillBeSlapped")] - public int ConfigVersion { get; set; } = CurrentConfigVersion; - - public const string ConfigFileName = "lighthouse.yml"; - public const string LegacyConfigFileName = LegacyServerSettings.ConfigFileName; - - #endregion Meta - - #region Setup - - private static readonly FileSystemWatcher fileWatcher; - - // ReSharper disable once NotNullMemberIsNotInitialized -#pragma warning disable CS8618 - static ServerConfiguration() - { - if (ServerStatics.IsUnitTesting) return; // Unit testing, we don't want to read configurations here since the tests will provide their own - - Logger.Info("Loading config...", LogArea.Config); - - ServerConfiguration? tempConfig; - - // If a valid YML configuration is available! - if (File.Exists(ConfigFileName) && (tempConfig = fromFile(ConfigFileName)) != null) - { - Instance = tempConfig; - - if (Instance.ConfigVersion < CurrentConfigVersion) - { - Logger.Info($"Upgrading config file from version {Instance.ConfigVersion} to version {CurrentConfigVersion}", LogArea.Config); - Instance.ConfigVersion = CurrentConfigVersion; - - Instance.writeConfig(ConfigFileName); - } - } - // If we have a valid legacy configuration we can migrate, let's do it now. - else if (File.Exists(LegacyConfigFileName)) - { - Logger.Warn("This version of Project Lighthouse now uses YML instead of JSON to store configuration.", LogArea.Config); - Logger.Warn - ("As such, the config will now be migrated to use YML. Do not modify the original JSON file; changes will not be kept.", LogArea.Config); - Logger.Info($"The new configuration is stored at {ConfigFileName}.", LogArea.Config); - - LegacyServerSettings? legacyConfig = LegacyServerSettings.FromFile(LegacyConfigFileName); - Debug.Assert(legacyConfig != null); - Instance = legacyConfig.ToNewConfiguration(); - - Instance.writeConfig(ConfigFileName); - - Logger.Success("The configuration migration completed successfully.", LogArea.Config); - } - // If there is no valid YML configuration available, - // generate a blank one and ask the server operator to configure it, then exit. - else - { - new ServerConfiguration().writeConfig(ConfigFileName + ".configme"); - - Logger.Warn - ( - "The configuration file was not found. " + - "A blank configuration file has been created for you at " + - $"{Path.Combine(Environment.CurrentDirectory, ConfigFileName + ".configme")}", - LogArea.Config - ); - - Environment.Exit(1); - } - - // Set up reloading - if (!Instance.ConfigReloading) return; - - Logger.Info("Setting up config reloading...", LogArea.Config); - fileWatcher = new FileSystemWatcher - { - Path = Environment.CurrentDirectory, - Filter = ConfigFileName, - NotifyFilter = NotifyFilters.LastWrite, // only watch for writes to config file - }; - - fileWatcher.Changed += onConfigChanged; // add event handler - - fileWatcher.EnableRaisingEvents = true; // begin watching - } -#pragma warning restore CS8618 - - private static void onConfigChanged(object sender, FileSystemEventArgs e) - { - try - { - fileWatcher.EnableRaisingEvents = false; - Debug.Assert(e.Name == ConfigFileName); - Logger.Info("Configuration file modified, reloading config...", LogArea.Config); - Logger.Warn("Some changes may not apply; they will require a restart of Lighthouse.", LogArea.Config); - - ServerConfiguration? configuration = fromFile(ConfigFileName); - if (configuration == null) - { - Logger.Warn("The new configuration was unable to be loaded for some reason. The old config has been kept.", LogArea.Config); - return; - } - - Instance = configuration; - - Logger.Success("Successfully reloaded the configuration!", LogArea.Config); - } - finally - { - fileWatcher.EnableRaisingEvents = true; - } - } - - private static INamingConvention namingConvention = CamelCaseNamingConvention.Instance; - - private static ServerConfiguration? fromFile(string path) - { - IDeserializer deserializer = new DeserializerBuilder().WithNamingConvention(namingConvention).IgnoreUnmatchedProperties().Build(); - - string text; - - try - { - text = File.ReadAllText(path); - } - catch - { - return null; - } - - return deserializer.Deserialize(text); - } - - private void writeConfig(string path) - { - ISerializer serializer = new SerializerBuilder().WithNamingConvention(namingConvention).Build(); - - File.WriteAllText(path, serializer.Serialize(this)); - } - - #endregion + public override int ConfigVersion { get; set; } = 16; + public override string ConfigName { get; set; } = "lighthouse.yml"; public string WebsiteListenUrl { get; set; } = "http://localhost:10060"; public string GameApiListenUrl { get; set; } = "http://localhost:10061"; public string ApiListenUrl { get; set; } = "http://localhost:10062"; @@ -177,7 +23,6 @@ public class ServerConfiguration public string RedisConnectionString { get; set; } = "redis://localhost:6379"; public string ExternalUrl { get; set; } = "http://localhost:10060"; public string GameApiExternalUrl { get; set; } = "http://localhost:10060/LITTLEBIGPLANETPS3_XML"; - public bool ConfigReloading { get; set; } public string EulaText { get; set; } = ""; #if !DEBUG public string AnnounceText { get; set; } = "You are now logged in as %user."; @@ -201,4 +46,6 @@ public class ServerConfiguration public CustomizationConfiguration Customization { get; set; } = new(); public RateLimitConfiguration RateLimitConfiguration { get; set; } = new(); public TwoFactorConfiguration TwoFactorConfiguration { get; set; } = new(); + + public override ConfigurationBase Deserialize(IDeserializer deserializer, string text) => deserializer.Deserialize(text); } \ No newline at end of file diff --git a/ProjectLighthouse/Helpers/VersionHelper.cs b/ProjectLighthouse/Helpers/VersionHelper.cs index f78bf858..35a20327 100644 --- a/ProjectLighthouse/Helpers/VersionHelper.cs +++ b/ProjectLighthouse/Helpers/VersionHelper.cs @@ -8,13 +8,12 @@ public static class VersionHelper { static VersionHelper() { - string commitNumber = "invalid"; try { CommitHash = ResourceHelper.ReadManifestFile("gitVersion.txt"); Branch = ResourceHelper.ReadManifestFile("gitBranch.txt"); - commitNumber = $"{CommitHash}_{Build}"; - FullRevision = (Branch == "main") ? $"r{commitNumber}" : $"{Branch}_r{commitNumber}"; + string commitNumber = $"{CommitHash}_{Build}"; + FullRevision = Branch == "main" ? $"r{commitNumber}" : $"{Branch}_r{commitNumber}"; string remotesFile = ResourceHelper.ReadManifestFile("gitRemotes.txt"); @@ -35,7 +34,6 @@ public static class VersionHelper Logger.Error ( "Project Lighthouse was built incorrectly. Please make sure git is available when building.", -// "Because of this, you will not be notified of updates.", LogArea.Startup ); CommitHash = "invalid"; @@ -43,16 +41,15 @@ public static class VersionHelper CanCheckForUpdates = false; } - if (IsDirty) - { - Logger.Warn - ( - "This is a modified version of Project Lighthouse. " + - "Please make sure you are properly disclosing the source code to any users who may be using this instance.", - LogArea.Startup - ); - CanCheckForUpdates = false; - } + if (!IsDirty) return; + + Logger.Warn + ( + "This is a modified version of Project Lighthouse. " + + "Please make sure you are properly disclosing the source code to any users who may be using this instance.", + LogArea.Startup + ); + CanCheckForUpdates = false; } public static string CommitHash { get; set; } diff --git a/ProjectLighthouse/Logging/Loggers/AspNet/AspNetToLighthouseLoggerProvider.cs b/ProjectLighthouse/Logging/Loggers/AspNet/AspNetToLighthouseLoggerProvider.cs index 9fc6eff6..3a076159 100644 --- a/ProjectLighthouse/Logging/Loggers/AspNet/AspNetToLighthouseLoggerProvider.cs +++ b/ProjectLighthouse/Logging/Loggers/AspNet/AspNetToLighthouseLoggerProvider.cs @@ -5,7 +5,7 @@ using IAspLogger = Microsoft.Extensions.Logging.ILogger; namespace LBPUnion.ProjectLighthouse.Logging.Loggers.AspNet; [ProviderAlias("Kettu")] -public class AspNetToLighthouseLoggerProvider : ILoggerProvider, IDisposable +public class AspNetToLighthouseLoggerProvider : ILoggerProvider { public void Dispose() { diff --git a/ProjectLighthouse/Logging/Loggers/ConsoleLogger.cs b/ProjectLighthouse/Logging/Loggers/ConsoleLogger.cs index fda35fb9..92693d05 100644 --- a/ProjectLighthouse/Logging/Loggers/ConsoleLogger.cs +++ b/ProjectLighthouse/Logging/Loggers/ConsoleLogger.cs @@ -8,55 +8,22 @@ public class ConsoleLogger : ILogger public void Log(LogLine logLine) { ConsoleColor oldForegroundColor = Console.ForegroundColor; + ConsoleColor logColor = logLine.Level.ToColor(); foreach (string line in logLine.Message.Split('\n')) { - // The following is scuffed. - // Beware~ - string time = DateTime.Now.ToString("MM/dd/yyyy-HH:mm:ss.fff"); - Console.ForegroundColor = ConsoleColor.White; - Console.Write('['); - Console.ForegroundColor = logLine.Level.ToColor(); - Console.Write(time); - Console.ForegroundColor = ConsoleColor.White; - Console.Write(']'); - Console.Write(' '); - - // Write the level! [Success] - Console.ForegroundColor = ConsoleColor.White; - Console.Write('['); - Console.ForegroundColor = logLine.Level.ToColor(); - Console.Write(logLine.Area); - Console.ForegroundColor = ConsoleColor.White; - Console.Write(':'); - Console.ForegroundColor = logLine.Level.ToColor(); - Console.Write(logLine.Level); - Console.ForegroundColor = ConsoleColor.White; - Console.Write(']'); - Console.ForegroundColor = oldForegroundColor; - Console.Write(' '); - + string trace = ""; if (logLine.Trace.Name != null) { - Console.ForegroundColor = ConsoleColor.White; - Console.Write('<'); - Console.ForegroundColor = logLine.Level.ToColor(); - Console.Write(logLine.Trace.Name); - if (logLine.Trace.Section != null) - { - Console.ForegroundColor = ConsoleColor.White; - Console.Write(':'); - Console.ForegroundColor = logLine.Level.ToColor(); - Console.Write(logLine.Trace.Section); - } - Console.ForegroundColor = ConsoleColor.White; - Console.Write('>'); - Console.Write(' '); - Console.ForegroundColor = oldForegroundColor; + trace += logLine.Trace.Name; + if (logLine.Trace.Section != null) trace += ":" + logLine.Trace.Section; + trace = "[" + trace + "]"; } - Console.WriteLine(line); + Console.ForegroundColor = logColor; + Console.WriteLine(@$"[{time}] [{logLine.Area}/{logLine.Level.ToString().ToUpper()}] {trace}: {line}"); + Console.ForegroundColor = oldForegroundColor; } } } \ No newline at end of file diff --git a/ProjectLighthouse/Configuration/PrivacySettings.cs b/ProjectLighthouse/PlayerData/PrivacySettings.cs similarity index 84% rename from ProjectLighthouse/Configuration/PrivacySettings.cs rename to ProjectLighthouse/PlayerData/PrivacySettings.cs index e5ec67ea..de57b9a1 100644 --- a/ProjectLighthouse/Configuration/PrivacySettings.cs +++ b/ProjectLighthouse/PlayerData/PrivacySettings.cs @@ -1,10 +1,8 @@ #nullable enable -using System; using System.Xml.Serialization; -using LBPUnion.ProjectLighthouse.PlayerData.Profiles; using LBPUnion.ProjectLighthouse.Serialization; -namespace LBPUnion.ProjectLighthouse.Configuration; +namespace LBPUnion.ProjectLighthouse.PlayerData; [XmlRoot("privacySettings")] [XmlType("privacySettings")] diff --git a/ProjectLighthouse/StartupTasks.cs b/ProjectLighthouse/StartupTasks.cs index 53e780d8..45151680 100644 --- a/ProjectLighthouse/StartupTasks.cs +++ b/ProjectLighthouse/StartupTasks.cs @@ -2,6 +2,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Reflection; +using System.Threading; using LBPUnion.ProjectLighthouse.Administration; using LBPUnion.ProjectLighthouse.Administration.Maintenance; using LBPUnion.ProjectLighthouse.Configuration; @@ -32,10 +34,16 @@ public static class StartupTasks Logger.Instance.AddLogger(new FileLogger()); Logger.Info($"Welcome to the Project Lighthouse {serverType.ToString()}!", LogArea.Startup); - Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup); - // Referencing ServerConfiguration.Instance here loads the config, see ServerConfiguration.cs for more information - Logger.Success("Loaded config file version " + ServerConfiguration.Instance.ConfigVersion, LogArea.Startup); + Logger.Info("Loading configurations...", LogArea.Startup); + if (!loadConfigurations()) + { + Logger.Error("Failed to load one or more configurations", LogArea.Config); + Environment.Exit(1); + } + + // Version info depends on ServerConfig + Logger.Info($"You are running version {VersionHelper.FullVersion}", LogArea.Startup); Logger.Info("Connecting to the database...", LogArea.Startup); bool dbConnected = ServerStatics.DbConnected; @@ -55,7 +63,7 @@ public static class StartupTasks if (serverType == ServerType.GameServer) #endif migrateDatabase(database); - + if (ServerConfiguration.Instance.InfluxDB.InfluxEnabled) { Logger.Info("Influx logging is enabled. Starting influx logging...", LogArea.Startup); @@ -84,10 +92,10 @@ public static class StartupTasks { FileHelper.ConvertAllTexturesToPng(); } - + Logger.Info("Initializing Redis...", LogArea.Startup); RedisDatabase.Initialize().Wait(); - + Logger.Info("Initializing repeating tasks...", LogArea.Startup); RepeatingTaskHandler.Initialize(); @@ -111,6 +119,41 @@ public static class StartupTasks Logger.Success($"Ready! Startup took {stopwatch.ElapsedMilliseconds}ms. Passing off control to ASP.NET...", LogArea.Startup); } + private static bool loadConfigurations() + { + Assembly assembly = Assembly.GetAssembly(typeof(ConfigurationBase<>)); + if (assembly == null) return false; + bool didLoad = true; + foreach (Type type in assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.BaseType?.Name == "ConfigurationBase`1")) + { + if (type.BaseType == null) continue; + if (type.BaseType.GetProperty("Instance") != null) + { + // force create lazy instance + type.BaseType.GetProperty("Instance")?.GetValue(null); + bool isConfigured = false; + while (!isConfigured) + { + isConfigured = (bool)(type.BaseType.GetProperty("IsConfigured")?.GetValue(null) ?? false); + Thread.Sleep(10); + } + } + + object objRef = type.BaseType.GetProperty("Instance")?.GetValue(null); + int configVersion = ((int?)type.GetProperty("ConfigVersion")?.GetValue(objRef)).GetValueOrDefault(); + if (configVersion <= 0) + { + didLoad = false; + } + else + { + Logger.Success($"Successfully loaded {type.Name} version {configVersion}", LogArea.Startup); + } + } + + return didLoad; + } + private static void migrateDatabase(Database database) { Logger.Info("Migrating database...", LogArea.Database);