diff --git a/ProjectLighthouse/Helpers/SMTPHelper.cs b/ProjectLighthouse/Helpers/SMTPHelper.cs index f0404fe5..e5a6a034 100644 --- a/ProjectLighthouse/Helpers/SMTPHelper.cs +++ b/ProjectLighthouse/Helpers/SMTPHelper.cs @@ -1,48 +1,106 @@ using System; +using System.Collections.Concurrent; using System.Net; using System.Net.Mail; +using System.Threading; +using System.Threading.Tasks; using LBPUnion.ProjectLighthouse.Configuration; +using LBPUnion.ProjectLighthouse.Logging; namespace LBPUnion.ProjectLighthouse.Helpers; -public static class SMTPHelper +public class SMTPHelper { - private static readonly SmtpClient client; - private static readonly MailAddress fromAddress; - static SMTPHelper() + internal static readonly SMTPHelper Instance = new(); + + private readonly SmtpClient client; + private readonly MailAddress fromAddress; + + private readonly ConcurrentQueue emailQueue = new(); + + private readonly SemaphoreSlim emailSemaphore = new(0); + + private bool stopSignal; + + private readonly Task emailThread; + + private SMTPHelper() { if (!ServerConfiguration.Instance.Mail.MailEnabled) return; - client = new SmtpClient(ServerConfiguration.Instance.Mail.Host, ServerConfiguration.Instance.Mail.Port) + this.client = new SmtpClient(ServerConfiguration.Instance.Mail.Host, ServerConfiguration.Instance.Mail.Port) { EnableSsl = ServerConfiguration.Instance.Mail.UseSSL, Credentials = new NetworkCredential(ServerConfiguration.Instance.Mail.Username, ServerConfiguration.Instance.Mail.Password), }; + this.fromAddress = new MailAddress(ServerConfiguration.Instance.Mail.FromAddress, ServerConfiguration.Instance.Mail.FromName); - fromAddress = new MailAddress(ServerConfiguration.Instance.Mail.FromAddress, ServerConfiguration.Instance.Mail.FromName); + this.stopSignal = false; + this.emailThread = Task.Factory.StartNew(this.EmailQueue); + } + + private async void EmailQueue() + { + while (!this.stopSignal) + { + await this.emailSemaphore.WaitAsync(); + if (!this.emailQueue.TryDequeue(out EmailEntry entry)) continue; + + try + { + this.client.Send(entry.Message); + entry.Result.SetResult(true); + } + catch (Exception e) + { + Logger.Error($"Failed to send email: {e}", LogArea.Email); + entry.Result.SetResult(false); + } + } + } + + public static void Dispose() + { + Instance.stopSignal = true; + Instance.emailThread.Wait(); + Instance.emailThread.Dispose(); } public static bool SendEmail(string recipientAddress, string subject, string body) { - if (!ServerConfiguration.Instance.Mail.MailEnabled) return false; + TaskCompletionSource resultTask = new(); + Instance.SendEmail(recipientAddress, subject, body, resultTask); + return resultTask.Task.Result; + } - MailMessage message = new(fromAddress, new MailAddress(recipientAddress)) + public void SendEmail(string recipientAddress, string subject, string body, TaskCompletionSource resultTask) + { + if (!ServerConfiguration.Instance.Mail.MailEnabled) + { + resultTask.SetResult(false); + return; + } + + MailMessage message = new(Instance.fromAddress, new MailAddress(recipientAddress)) { Subject = subject, Body = body, }; - try - { - client.Send(message); - } - catch(Exception e) - { - Console.WriteLine(e); - return false; - } - - return true; + this.emailQueue.Enqueue(new EmailEntry(message, resultTask)); + this.emailSemaphore.Release(); } + + internal class EmailEntry + { + public MailMessage Message { get; set; } + public TaskCompletionSource Result { get; set; } + + public EmailEntry(MailMessage message, TaskCompletionSource result) + { + this.Message = message; + this.Result = result; + } + } } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/LogArea.cs b/ProjectLighthouse/Logging/LogArea.cs index 145c36be..a0fa18c7 100644 --- a/ProjectLighthouse/Logging/LogArea.cs +++ b/ProjectLighthouse/Logging/LogArea.cs @@ -26,4 +26,5 @@ public enum LogArea Score, RateLimit, Deserialization, + Email, } \ No newline at end of file diff --git a/ProjectLighthouse/Logging/Logger.cs b/ProjectLighthouse/Logging/Logger.cs index b30473b8..690a65fc 100644 --- a/ProjectLighthouse/Logging/Logger.cs +++ b/ProjectLighthouse/Logging/Logger.cs @@ -73,6 +73,11 @@ public class Logger /// private readonly ConcurrentQueue logQueue = new(); + /// + /// Used to signal the logging loop that a new message has arrived + /// + private readonly SemaphoreSlim logSemaphore = new(0); + /// /// Adds a to the queue. Only used internally. /// @@ -86,17 +91,12 @@ public class Logger public Logger() // Start queue thread on first Logger access { Task.Factory.StartNew - ( - () => + (async () => { while (true) { - bool logged = this.queueLoop(); - Thread.Sleep(logged ? 10 : 100); - // We wait 100ms if we dont log since it's less likely that the program logged again. - // If we did log, wait 10ms before looping again. - - // This is all so we use as little CPU as possible. This is an endless while loop, after all. + await this.logSemaphore.WaitAsync(); + this.queueLoop(); } } ); @@ -133,16 +133,14 @@ public class Logger /// A function used by the queue thread /// /// - private bool queueLoop() + private void queueLoop() { - if (!this.logQueue.TryDequeue(out LogLine line)) return false; + if (!this.logQueue.TryDequeue(out LogLine line)) return; foreach (ILogger logger in this.loggers) { logger.Log(line); } - - return true; } #endregion @@ -215,6 +213,7 @@ public class Logger Area = area, Trace = getTrace(extraTraceLines), }); + this.logSemaphore.Release(); } #endregion } \ No newline at end of file