diff --git a/Common/PendingEmails/App.config b/Common/PendingEmails/App.config index f0c21a8d..ec39cb3b 100644 --- a/Common/PendingEmails/App.config +++ b/Common/PendingEmails/App.config @@ -1,10 +1,12 @@  - - + + + + + + + - - - \ No newline at end of file diff --git a/Common/PendingEmails/AppSettings.cs b/Common/PendingEmails/AppSettings.cs new file mode 100644 index 00000000..715ac93a --- /dev/null +++ b/Common/PendingEmails/AppSettings.cs @@ -0,0 +1,138 @@ +namespace PendingEmails +{ + using System; + using System.Collections.Generic; + using System.Configuration; + using System.IO; + using System.Linq; + + public static partial class Program + { + public class AppSettings + { + public static string LogFolder { get; private set; } + + public static string MSSQLConnectionString { get; private set; } + + public static string Sender { get; private set; } + + public static string ExcludeRecipients { get; private set; } + + public static string SMTPHost { get; private set; } + + public static int SMTPPort { get; private set; } + + public static int Interval { get; private set; } + + public static bool TryLoadExeConfiguration(out ICollection errors) + { + errors = new HashSet(); + + var exeFileInfo = new FileInfo(typeof(Program).Assembly.Location); + var settings = ConfigurationManager + .OpenExeConfiguration(exeFileInfo.FullName) + ?.AppSettings + ?.Settings; + var loaded = true; + + if (settings is null) + { + errors.Add($"{exeFileInfo.Name} nicht gefunden."); + + loaded = false; + } + else + { + var keys = settings.AllKeys; + + LogFolder = Environment.GetFolderPath(Environment.SpecialFolder.Desktop); + + if (!keys.Any(x => x == nameof(LogFolder))) + { + errors.Add($"{nameof(LogFolder)} nicht definiert in die {exeFileInfo.Name}."); + } + else + { + LogFolder = settings[nameof(LogFolder)]?.Value; + } + + if (!keys.Any(x => x == nameof(MSSQLConnectionString))) + { + errors.Add($"{nameof(MSSQLConnectionString)} nicht definiert in die {exeFileInfo.Name}."); + + loaded = false; + } + else + { + MSSQLConnectionString = settings[nameof(MSSQLConnectionString)]?.Value; + } + + if (!keys.Any(x => x == nameof(Sender))) + { + errors.Add($"{nameof(Sender)} nicht definiert in die {exeFileInfo.Name}."); + + loaded = false; + } + else + { + Sender = settings[nameof(Sender)]?.Value; + } + + if (!keys.Any(x => x == nameof(ExcludeRecipients))) + { + errors.Add($"{nameof(ExcludeRecipients)} nicht definiert in die {exeFileInfo.Name}."); + } + else + { + ExcludeRecipients = settings[nameof(ExcludeRecipients)]?.Value; + } + + if (!keys.Any(x => x == nameof(SMTPHost))) + { + errors.Add($"{nameof(SMTPHost)} nicht definiert in die {exeFileInfo.Name}."); + + loaded = false; + } + else + { + SMTPHost = settings[nameof(SMTPHost)]?.Value; + } + + if (!keys.Any(x => x == nameof(SMTPPort))) + { + errors.Add($"{nameof(SMTPPort)} nicht definiert in die {exeFileInfo.Name}."); + + loaded = false; + } + else if (!int.TryParse($"{settings[nameof(SMTPPort)]?.Value}", out var smtpPort)) + { + errors.Add($"{nameof(SMTPPort)} ist ungültig."); + + loaded = false; + } + else + { + SMTPPort = smtpPort; + } + + Interval = 600_000; + + if (!keys.Any(x => x == nameof(Interval))) + { + errors.Add($"{nameof(Interval)} nicht definiert in die {exeFileInfo.Name}."); + } + else if (!int.TryParse($"{settings[nameof(Interval)]?.Value}", out var interval)) + { + errors.Add($"{nameof(Interval)} ist ungültig."); + } + else + { + Interval = interval; + } + } + + return loaded; + } + } + } +} diff --git a/Common/PendingEmails/Message.cs b/Common/PendingEmails/Message.cs new file mode 100644 index 00000000..023fc9b5 --- /dev/null +++ b/Common/PendingEmails/Message.cs @@ -0,0 +1,32 @@ +namespace PendingEmails +{ + public static partial class Program + { + public class Message + { + public Message(int messageId, string sender, string subject, string body, string recipients, string recipientsCC) + { + this.Id = messageId; + this.Sender = sender; + this.Subject = subject; + this.Body = body; + this.Recipients = recipients; + this.RecipientsCC = recipientsCC; + } + + public int Id { get; set; } + + public string Sender { get; set; } + + public string Subject { get; set; } + + public string Body { get; set; } + + public string Recipients { get; set; } + + public string RecipientsCC { get; set; } + + public bool IsValid => !string.IsNullOrWhiteSpace(this.Recipients); + } + } +} diff --git a/Common/PendingEmails/PendingEmails.csproj b/Common/PendingEmails/PendingEmails.csproj index 9813bb46..776612d3 100644 --- a/Common/PendingEmails/PendingEmails.csproj +++ b/Common/PendingEmails/PendingEmails.csproj @@ -37,6 +37,7 @@ DEBUG;TRACE prompt 4 + IDE0003;IDE0049; AnyCPU @@ -69,10 +70,18 @@ true + + + + + sensus.ico + + + @@ -80,6 +89,8 @@ + + @@ -93,5 +104,8 @@ false + + + \ No newline at end of file diff --git a/Common/PendingEmails/Program.cs b/Common/PendingEmails/Program.cs index f935bd2c..3ec08b23 100644 --- a/Common/PendingEmails/Program.cs +++ b/Common/PendingEmails/Program.cs @@ -1,273 +1,269 @@ -namespace PendingEmails -{ - using System; - using System.Collections.Generic; - using System.Configuration; - using System.Data; - using System.Data.SqlClient; - using System.IO; - using System.Linq; - using System.Net.Mail; - using System.Threading; - using System.Timers; - - using SystemTimersTimer = System.Timers.Timer; - - public static class Program - { - private static readonly SystemTimersTimer timer = new SystemTimersTimer(); - - private static void Main() - { - Console.WriteLine("Pending emails ...."); - try - { - timer.AutoReset = true; - timer.Interval = 600_000; - timer.Elapsed += Timer_Elapsed; - - while (true) - { - Timer_Elapsed(null, null); - timer.Start(); - var awaiter = new ManualResetEventSlim(); - awaiter.Wait(); - timer.Stop(); - } - } - catch (Exception e) - { - LogToFile(e.Message); - } - - } - - public class Ready - { - public string Source { get; set; } - - public string Subject { get; set; } - - public string Body { get; set; } - } - - private static void Timer_Elapsed(Object _, ElapsedEventArgs _1) - { - LogToFile($"loading pending emails"); - - var connectionString = ConfigurationManager.ConnectionStrings["Auftrag"].ConnectionString; - - using (var connection = new SqlConnection(connectionString)) - { - connection.FireInfoMessageEventOnUserErrors = true; - connection.InfoMessage += (_2, args) => LogToFile(args.Message); - connection.Open(); - - var ready = new List(); - - using (var command = connection.CreateCommand()) - { - command.CommandTimeout = 60000; - command.CommandText = @" - INSERT INTO Messages - ( Source - , InDate - , StationNr - , PCName - , IPAddress - , Trace - , Sender - , Subject - , Body - , Recipients - , CC - , State) - SELECT ag.Fertigungsauftrag - , CURRENT_TIMESTAMP - , ag.Pruefstation - , @@SERVERNAME - , @@SERVICENAME - , 'Xylem.Common.Service.PendingMails' - , 'noreplay@xylem.com' - , ISNULL(ag.Kundenort, 'Kundenort') + ' - ' + CAST(ag.Auftrag AS VARCHAR(15)) + '/' + CAST(ag.Position AS VARCHAR(10)) - + ' erfolgreich geprüft - bitte Garantiescheine drucken und bereitlegen!' - , 'Auftrag: ' + CAST(ag.Auftrag AS VARCHAR(15)) + '/' + CAST(ag.Position AS VARCHAR(10)) + CHAR(10) + 'FertigungsauftragNr: ' + CAST(ISNULL(ag.Fertigungsauftrag, 0) AS VARCHAR(15)) + CHAR(10) - + CHAR(10) + CAST(ISNULL(ag.Menge, 0) AS VARCHAR(10)) + ' Stück ' + ag.Bezeichnung + CHAR(10) + 'wurde an der Prüfstation: ' + CAST(ISNULL(ag.Pruefstation, 0) AS VARCHAR(5)) + CHAR(10) + 'am ' + CONVERT(VARCHAR(10), ag.Pruefdatum, 105) + ' um ' + CONVERT(VARCHAR(5), ag.Pruefdatum, 108) + ' Uhr' + CHAR(10) + 'von ' + ag.Mitarbaiter + ' vollständig geprüft.' + CHAR(10) - + CHAR(10) + 'Seriennummern: ' + CAST(ag.SerienNrVon AS VARCHAR(15)) + ' - ' + CAST(ag.SerienNrBis AS VARCHAR(15)) + '' + CHAR(10) - + CHAR(10) - + ag.Merkmal + CHAR(10) - , 'uwe.kubon@xylem.com' - , 'stoyan.zlatev@xylem.com' - , 'Automatische fertigmeldung für garantiescheine.' - FROM AlleGepruefteAuftraegeMitGarantieAusLetzte24Hours AS ag - LEFT JOIN Messages AS msg - ON ag.Fertigungsauftrag LIKE msg.Source - WHERE msg.Id IS NULL"; - - LogToFile($"Inserted: {command.ExecuteNonQuery()}"); - } - - var updated = new Dictionary(); - var countAll = 0; - - using (var command = connection.CreateCommand()) - { - command.CommandText = @" - SELECT [Sender] - , [Subject] - , [Body] - , [Recipients] - , [CC] - , [Id] - FROM [Messages] - WHERE [OutDate] IS NULL - ORDER BY [InDate] DESC"; - - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - var messageId = reader.GetValue(5); - - try - { - var email = new MailMessage(); - var from = ConfigurationManager.AppSettings["DefaultSender"]; - email.From = new MailAddress(from); - email.Subject = $"{reader.GetValue(1)}"; - email.Body = $"{reader.GetValue(2)}"; - - $"{reader.GetValue(3)}" - .Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => new MailAddress(x)) - .ToList() - .ForEach(email.To.Add); - - $"{reader.GetValue(4)}" - .Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries) - .Select(x => new MailAddress(x)) - .ToList() - .ForEach(email.CC.Add); - - if (SendEmail(email)) - { - updated[messageId] = "Sent"; - } - } - catch (Exception e) - { - LogToFile(e); - - updated[messageId] = e.ToString(); - } - - countAll++; - } - } - } - - var updatedCount = updated.Count; - - if (updatedCount > 0) - { - try - { - LogToFile($"updating: {string.Join(", ", updated)}"); - - foreach (var kvp in updated) - { - using (var command = connection.CreateCommand()) - { - command.CommandText = $@" - UPDATE [Messages] - SET [OutDate] = GETDATE() - , [State] = ISNULL([State], '') + ISNULL(@{nameof(kvp.Value)}, '') - WHERE [Id] = @{nameof(kvp.Key)}"; - command.Parameters.Add(new SqlParameter(nameof(kvp.Key), kvp.Key)); - command.Parameters.Add(new SqlParameter(nameof(kvp.Value), kvp.Value)); - command.ExecuteNonQuery(); - command.Parameters.Clear(); - } - - updatedCount++; - } - - Console.WriteLine($"{updatedCount}/{countAll} email{(updatedCount == 1 ? null : "s")} sent."); - LogToFile($"{updatedCount}/{countAll} emails sent."); - } - catch (Exception e) - { - LogToFile(e); - } - } - else - { - LogToFile($"there are no pending emails to send"); - } - } - } - - private static Boolean SendEmail(MailMessage email) - { - try - { - using (var smtpClient = new SmtpClient("smtp.xylem.com", 587)) - { - smtpClient.UseDefaultCredentials = true; - smtpClient.Send(email); - } - - return true; - } - catch (Exception e) - { - LogToFile(e); - } - - return false; - } - - private static void LogToFile(Object e) - => File.AppendAllText(GetOrCreateFile(), $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {e}{Environment.NewLine}"); - - private static String GetOrCreateFile() - { - var logFolder = ConfigurationManager.AppSettings["LogFolder"]; - var logPath = Directory.CreateDirectory(logFolder).FullName; - - return Path.Combine(logPath, $"{DateTime.Now:yyyy-MM-dd}.txt"); - } - - private static T GetValue(this SqlDataReader sqlDataReader, Int32 index, T defaultValue = default(T)) - { - var value = defaultValue; - - if (0 <= index && index < sqlDataReader.FieldCount && !sqlDataReader.IsDBNull(index)) - { - var sqlValue = sqlDataReader.GetValue(index); - var type = Nullable.GetUnderlyingType(typeof(T)); - - if (type is null) - { - type = typeof(T); - } - - try - { - sqlValue = Convert.ChangeType(sqlValue, type); - - if (sqlValue is T expectedValue) - { - value = expectedValue; - } - } - catch (Exception e) - { - LogToFile(e); - } - } - - return value; - } - } -} +namespace PendingEmails +{ + using System; + using System.Collections.Generic; + using System.Data; + using System.Data.SqlClient; + using System.IO; + using System.Linq; + using System.Net.Mail; + using System.Threading; + + public static partial class Program + { + private static void Main() + { + try + { + var awaiter = new ManualResetEventSlim(); + + while (true) + { + if (!AppSettings.TryLoadExeConfiguration(out var errors)) + { + WriteLine(new string('=', 50)); + + foreach (var error in errors) + { + WriteLine(error); + } + } + else + { + WriteLine(new string('=', 50)); + + using (var connection = new SqlConnection(AppSettings.MSSQLConnectionString)) + { + OpenConnection(connection); + + FindWarantyEmails(connection); + var messages = FindPendingEmails(connection); + + if (messages.Count <= 0) + { + WriteLine($"Keine neue nachrichten gefunden."); + } + + var invalidMessages = messages.Where(x => !x.IsValid).ToList(); + + if (invalidMessages.Count > 0) + { + WriteLine($"{invalidMessages.Count} von {messages.Count} ungültige nachrichten gefunden."); + var rowsAffected = SetOutDate(connection, invalidMessages.Select(x => x.Id)); + WriteLine($"{rowsAffected} von {invalidMessages.Count} wurden als gesended markiert."); + } + else + { + WriteLine($"Keine neue ungültige nachrichten gefunden."); + } + + var validMessages = messages + .Where(x => x.IsValid) + .ToList(); + + if (validMessages.Count > 0) + { + WriteLine($"{validMessages.Count} von {messages.Count} gültige nachrichten gefunden."); + SendEmails(connection, validMessages); + } + } + } + + awaiter.Wait(AppSettings.Interval); + awaiter.Reset(); + } + } + catch (Exception e) + { + WriteLine(e.ToString()); + } + } + + private static void OpenConnection(SqlConnection connection) + { + connection.FireInfoMessageEventOnUserErrors = true; + connection.InfoMessage += (_, args) => WriteLine(args.Message); + connection.Open(); + } + + private static void SendEmails(SqlConnection connection, List messages) + { + var sentEmails = new List(); + var excludedRecipients = AppSettings + .ExcludeRecipients + .ToEmailList(); + + foreach (var message in messages) + { + try + { + var recipients = message + .Recipients + .ToEmailList() + .Except(excludedRecipients) + .ToList(); + + if (recipients.Count <= 0) + { + break; + } + + var recipientsCC = message + .RecipientsCC + .ToEmailList() + .Except(excludedRecipients) + .ToList(); + + var emailMessage = new MailMessage + { + From = new MailAddress(AppSettings.Sender), + Subject = message.Subject, + Body = message.Body + }; + + recipients.ForEach(emailMessage.To.Add); + recipientsCC.ForEach(emailMessage.CC.Add); + + using (var smtpClient = new SmtpClient(AppSettings.SMTPHost, AppSettings.SMTPPort)) + { + smtpClient.UseDefaultCredentials = true; + smtpClient.Send(emailMessage); + } + + sentEmails.Add(message.Id); + WriteLine($"Nachricht: {message.Id} wurde gesendet."); + } + catch (Exception e) + { + WriteLine(e.ToString()); + } + } + + if (sentEmails.Count > 0) + { + WriteLine($"Gesendet {sentEmails.Count} von {messages.Count}"); + var rowsAffected = SetOutDate(connection, sentEmails); + WriteLine($"{rowsAffected} von {messages.Count} wurden als gesended markiert."); + } + else + { + WriteLine($"Keine nachrichten gesendet."); + } + } + + private static int SetOutDate(SqlConnection connection, IEnumerable messageIds) + { + var rowsAffected = default(int); + + using (var command = connection.CreateCommand()) + { + command.CommandText = $@" + UPDATE Messages + SET OutDate = GETDATE() + WHERE Id IN ({string.Join(", ", messageIds)})"; + rowsAffected = command.ExecuteNonQuery(); + } + + return rowsAffected; + } + + private static List FindPendingEmails(SqlConnection connection) + { + var messages = new List(); + + using (var command = connection.CreateCommand()) + { + command.CommandText = @" + SELECT Id + , Sender + , Subject + , Body + , Recipients + , CC + FROM Messages + WHERE OutDate IS NULL + ORDER BY InDate DESC"; + + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + var messageId = reader.GetValue(0); + var sender = reader.GetValue(1); + var subject = reader.GetValue(2); + var body = reader.GetValue(3); + var recipients = reader.GetValue(4); + var recipientsCC = reader.GetValue(5); + + messages.Add(new Message(messageId, sender, subject, body, recipients, recipientsCC)); + } + } + } + + return messages; + } + + private static void FindWarantyEmails(SqlConnection connection) + { + using (var command = connection.CreateCommand()) + { + command.CommandTimeout = 60000; + command.CommandType = CommandType.StoredProcedure; + command.CommandText = @"SP_EMAILS_FUER_GARANTIESCHEINE_LADEN"; + + WriteLine($"E-Mails für Garantiescheine gefunden: {command.ExecuteNonQuery()}"); + } + } + + private static void WriteLine(object e) + { + var logFolder = AppSettings.LogFolder; + var logPath = Directory.CreateDirectory(logFolder).FullName; + var logFile = Path.Combine(logPath, $"{DateTime.Now:yyyy-MM-dd}.txt"); + var message = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {e}"; + + File.AppendAllText(logFile, message); + Console.WriteLine(message.Trim()); + } + + private static T GetValue(this SqlDataReader sqlDataReader, int index, T defaultValue = default(T)) + { + var value = defaultValue; + + if (0 <= index && index < sqlDataReader.FieldCount && !sqlDataReader.IsDBNull(index)) + { + var sqlValue = sqlDataReader.GetValue(index); + var type = Nullable.GetUnderlyingType(typeof(T)); + + if (type is null) + { + type = typeof(T); + } + + try + { + sqlValue = Convert.ChangeType(sqlValue, type); + + if (sqlValue is T expectedValue) + { + value = expectedValue; + } + } + catch (Exception e) + { + WriteLine(e); + } + } + + return value; + } + + private static List ToEmailList(this string value) + => value + ?.ToLower() + ?.Split(new[] { ',', ';', ' ' }, StringSplitOptions.RemoveEmptyEntries) + ?.Where(x => !string.IsNullOrWhiteSpace(x)) + ?.ToList() + ?? new List(); + } +} diff --git a/Common/PendingEmails/sensus.ico b/Common/PendingEmails/sensus.ico new file mode 100644 index 00000000..c35853e5 Binary files /dev/null and b/Common/PendingEmails/sensus.ico differ