diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj index c08430a..ab90bce 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Dynamicweb.Ecommerce.DynamicwebLiveIntegration.csproj @@ -1,6 +1,6 @@  - 10.4.23 + 10.4.24 1.0.0.0 Live Integration Live Integration diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveContext.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveContext.cs index 44a3567..49e5ce5 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveContext.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveContext.cs @@ -22,7 +22,7 @@ public LiveContext(Currency currency, User user, Shop shop) Currency = currency; User = user; Shop = shop; - Country = GetCountry(user); + Country = Common.Context.Country ?? GetCountry(user); } public LiveContext(PriceContext priceContext) diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs index 6269f08..398d97f 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/LiveIntegrationAddIn.cs @@ -816,6 +816,7 @@ public override void SaveSettings() Licensing.LicenseService.SaveSettings(settings); SaveTranslations(settings); Connector.ClearCache(); + Logger.ClearLogMessages(settings); } /// diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Logging/Logger.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Logging/Logger.cs index 5a8b044..5c81b35 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Logging/Logger.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/Logging/Logger.cs @@ -4,6 +4,7 @@ using Dynamicweb.Mailing; using Dynamicweb.Rendering; using System; +using System.Collections.Concurrent; using System.IO; using System.Linq; using System.Threading; @@ -18,17 +19,16 @@ public class Logger { private static readonly string DateTimeFormat = "yyyy-MM-dd HH:mm:ss.ffff"; - /// - /// The synchronize lock. - /// - private static readonly object SyncLock = new(); - /// /// The log file /// private readonly string _logFile; private readonly Settings _settings; + private readonly TimeSpan _waitTimeout; + private readonly NotificationFrequency _notificationFrequency; + private static readonly ConcurrentDictionary> _lastLogMessages = new(); + private readonly int _lastLogMessagesLimit = 100; /// /// Prevents a default instance of the class from being created. @@ -36,13 +36,10 @@ public class Logger public Logger(Settings settings) { _settings = settings; - if (settings != null) - { - if (string.IsNullOrEmpty(_logFile)) - { - _logFile = SystemInformation.MapPath($"/Files/System/Log/LiveIntegration/{settings.InstanceName}.log"); - } - } + + _logFile = SystemInformation.MapPath($"/Files/System/Log/LiveIntegration/{settings.InstanceName}.log"); + _waitTimeout = TimeSpan.FromSeconds(settings.AutoPingInterval < Constants.MinPingInterval ? Constants.MinPingInterval : settings.AutoPingInterval); + _notificationFrequency = Helpers.GetEnumValueFromString(_settings.NotificationSendingFrequency, NotificationFrequency.Never); } /// @@ -69,74 +66,22 @@ public Logger(Settings settings) /// true if [log response errors]; otherwise, false. private bool LogResponseErrors => _settings.LogResponseErrors; - /// - /// Sends an mail with error information according to configuration. - /// - /// The error/success message to send. - /// true if email was sent, false otherwise. - public bool SendMail(string message) - { - string notificationTemplate = _settings.NotificationTemplate; - if (string.IsNullOrEmpty(message) || string.IsNullOrEmpty(notificationTemplate)) - return false; - - var recipients = _settings.NotificationRecipients; - if (recipients is null || !recipients.Any()) - return false; - - Template templateInstance = new($"/DataIntegration/Notifications/{notificationTemplate}"); - templateInstance.SetTag("Ecom:LiveIntegration.AddInName", Constants.AddInName); - templateInstance.SetTag("Ecom:LiveIntegration.ErrorMessage", message); - - string notificationEmailSubject = _settings.NotificationEmailSubject; - string notificationEmailSenderEmail = _settings.NotificationEmailSenderEmail; - string notificationEmailSenderName = _settings.NotificationEmailSenderName; - - using var mail = new System.Net.Mail.MailMessage(); - mail.IsBodyHtml = true; - mail.Subject = notificationEmailSubject; - mail.SubjectEncoding = System.Text.Encoding.UTF8; - mail.From = new(notificationEmailSenderEmail, notificationEmailSenderName, System.Text.Encoding.UTF8); - - // Set parameters for MailMessage - foreach (var email in recipients) - mail.To.Add(email); - mail.BodyEncoding = System.Text.Encoding.UTF8; - - // Render Template and set as Body - mail.Body = templateInstance.Output(); - - // Send mail - return EmailHandler.Send(mail); - } - /// /// Gets the error messages sine the last email was sent. /// - /// Log data + /// Log data public string GetLastLogData() { string result = string.Empty; - lock (SyncLock) + if (_lastLogMessages.TryGetValue(_logFile, out var queue)) { - foreach (var line in File.ReadLines(_logFile, System.Text.Encoding.UTF8).Reverse()) + var lines = queue.ToList(); + foreach (var line in lines) { - if (line.Contains(ErrorLevel.DebugInfo.ToString())) - { - // ignore debug info - } - else if (!line.Contains(ErrorLevel.EmailSend.ToString())) - { - result += line + "
"; - } - else - { - break; - } + result += line + "
"; } } - return result; } @@ -147,18 +92,22 @@ public string GetLastLogData() /// The log line. public void Log(ErrorLevel errorLevel, string logline) { - if (!string.IsNullOrEmpty(_logFile)) + bool isAllowedAddToLog = IsAllowedAddToLog(errorLevel); + if (isAllowedAddToLog) { - bool isAllowedAddToLog = IsAllowedAddToLog(errorLevel); - lock (SyncLock) + logline = $"{DateTime.Now.ToString(DateTimeFormat)}: {errorLevel}: {logline}{System.Environment.NewLine}"; + + TryAddLogLineToQueue(errorLevel, logline); + + using (var mutex = new Mutex(false, _logFile.Replace("\\", ""))) { - if (isAllowedAddToLog) + var hasHandle = false; + try { - KeepOrTruncateFile(); - logline = $"{DateTime.Now.ToString(DateTimeFormat)}: {errorLevel}: {logline}{System.Environment.NewLine}"; - + hasHandle = mutex.WaitOne(_waitTimeout, false); + KeepOrTruncateFile(); try - { + { TextFileHelper.WriteTextFile(logline, _logFile, true, System.Text.Encoding.UTF8); } catch (IOException) @@ -173,12 +122,39 @@ public void Log(ErrorLevel errorLevel, string logline) TextFileHelper.WriteTextFile(logline, fileName, false, System.Text.Encoding.UTF8); } } + catch (Exception) + { + throw; + } + finally + { + if (hasHandle) + mutex.ReleaseMutex(); + } } + } - if (errorLevel != ErrorLevel.DebugInfo && errorLevel != ErrorLevel.EmailSend) + if (errorLevel != ErrorLevel.DebugInfo && errorLevel != ErrorLevel.EmailSend) + { + SendLog(logline, isAllowedAddToLog); + } + } + + private void TryAddLogLineToQueue(ErrorLevel errorLevel, string logline) + { + if (errorLevel != ErrorLevel.DebugInfo && errorLevel != ErrorLevel.EmailSend && _notificationFrequency != NotificationFrequency.Never) + { + _lastLogMessages.AddOrUpdate(_logFile, + new ConcurrentQueue([logline]), + (k, q) => { - SendLog(logline, isAllowedAddToLog); - } + q.Enqueue(logline); + if (q.Count > _lastLogMessagesLimit) + { + q.TryDequeue(out _); + } + return q; + }); } } @@ -275,40 +251,33 @@ private void MoveToHistoryFile(FileInfo fi) /// if set to true [is last error in log]. private void SendLog(string lastError, bool isLastErrorInLog) { - string frequencySettings = _settings.NotificationSendingFrequency; - if (string.IsNullOrEmpty(frequencySettings)) - { - return; - } - - var frequency = Helpers.GetEnumValueFromString(frequencySettings, NotificationFrequency.Never); - if (frequency == NotificationFrequency.Never) + if (_notificationFrequency == NotificationFrequency.Never) { return; } // Get last time when the email was sent - DateTime lastTimeSend = Settings.LastNotificationEmailSent; + DateTime lastTimeSend = Settings.LastNotificationEmailSent; bool emailSent = false; if (lastTimeSend == DateTime.MinValue) { // used for getting the last errors appeared for the future email Settings.LastNotificationEmailSent = DateTime.Now; - emailSent = SendMail(lastError); + emailSent = SendMail(lastError, false); } else { // send email if the frequency interval already passed - if (DateTime.Now.Subtract(lastTimeSend) >= TimeSpan.FromMinutes((double)frequency)) + if (DateTime.Now.Subtract(lastTimeSend) >= TimeSpan.FromMinutes((double)_notificationFrequency)) { Settings.LastNotificationEmailSent = DateTime.Now; if (!isLastErrorInLog) { - emailSent = SendMail(GetLastLogData() + lastError); + emailSent = SendMail(lastError, true); } else { - emailSent = SendMail(GetLastLogData()); + emailSent = SendMail(null, true); } } } @@ -316,13 +285,73 @@ private void SendLog(string lastError, bool isLastErrorInLog) if (emailSent) { Log(ErrorLevel.EmailSend, "Send e-mail with errors"); + _lastLogMessages.TryRemove(_logFile, out _); } else - { + { Settings.LastNotificationEmailSent = lastTimeSend; } } + [Obsolete("Use SendMail(string message, bool getLastLogData) instead")] + public bool SendMail(string message) + { + return SendMail(message, true); + } + + /// + /// Sends an mail with error information according to configuration. + /// + /// The error/success message to send. + /// true if email was sent, false otherwise. + public bool SendMail(string message, bool getLastLogData) + { + string notificationTemplate = _settings.NotificationTemplate; + if (string.IsNullOrEmpty(notificationTemplate)) + return false; + + var recipients = _settings.NotificationRecipients; + if (recipients is null || recipients.Count == 0) + return false; + + Template templateInstance = new($"/DataIntegration/Notifications/{notificationTemplate}"); + templateInstance.SetTag("Ecom:LiveIntegration.AddInName", Constants.AddInName); + bool logMsgTagExists = templateInstance.TagExists("Ecom:LiveIntegration.ErrorMessage"); + if (logMsgTagExists) + { + if (getLastLogData) + { + message = GetLastLogData() + message; + } + templateInstance.SetTag("Ecom:LiveIntegration.ErrorMessage", message); + } + + string notificationEmailSubject = _settings.NotificationEmailSubject; + string notificationEmailSenderEmail = _settings.NotificationEmailSenderEmail; + if (!StringHelper.IsValidEmailAddress(notificationEmailSenderEmail)) + { + notificationEmailSenderEmail = EmailHandler.SystemMailFromAddress(); + } + string notificationEmailSenderName = _settings.NotificationEmailSenderName; + + using var mail = new System.Net.Mail.MailMessage(); + mail.IsBodyHtml = true; + mail.Subject = notificationEmailSubject; + mail.SubjectEncoding = System.Text.Encoding.UTF8; + mail.From = new(notificationEmailSenderEmail, notificationEmailSenderName, System.Text.Encoding.UTF8); + + // Set parameters for MailMessage + foreach (var email in recipients) + mail.To.Add(email); + mail.BodyEncoding = System.Text.Encoding.UTF8; + + // Render Template and set as Body + mail.Body = templateInstance.Output(); + + // Send mail + return EmailHandler.Send(mail); + } + /// /// Truncates the log file. /// @@ -375,5 +404,11 @@ private void TruncateLogFileFileInfo(FileInfo fi, int maxSize) TextFileHelper.WriteTextFile(logLine, _logFile, true, System.Text.Encoding.UTF8); } } + + internal static void ClearLogMessages(Settings settings) + { + var logFile = SystemInformation.MapPath($"/Files/System/Log/LiveIntegration/{settings.InstanceName}.log"); + _lastLogMessages.TryRemove(logFile, out _); + } } } \ No newline at end of file diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UrlHandler.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UrlHandler.cs index 960d65c..1565182 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UrlHandler.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/UrlHandler.cs @@ -15,15 +15,13 @@ namespace Dynamicweb.Ecommerce.DynamicwebLiveIntegration ///
public class UrlHandler { - /// - /// The synchronize root - /// - private static readonly object SyncRoot = new object(); + private static readonly UrlHandler _instance = new UrlHandler(); - /// - /// The instance - /// - private static UrlHandler _instance; + // Explicit static constructor to tell C# compiler + // not to mark type as beforefieldinit + static UrlHandler() + { + } /// /// Prevents a default instance of the class from being created. @@ -38,21 +36,7 @@ private UrlHandler() /// The instance. public static UrlHandler Instance { - get - { - if (_instance == null) - { - lock (SyncRoot) - { - if (_instance == null) - { - _instance = new UrlHandler(); - } - } - } - - return _instance; - } + get => _instance; } /// diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs index 6af86f5..52f76f5 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/OrderXmlGenerator.cs @@ -88,7 +88,7 @@ private void AddOrderDeliveryInformation(OrderXmlGeneratorSettings settings, Xml { deliveryAddress = UserManagementServices.UserAddresses.GetAddressById(order.DeliveryAddressId); } - string deliveryName = (deliveryAddress is object && !string.IsNullOrEmpty(order.DeliveryName)) ? order.DeliveryName : + string deliveryName = !string.IsNullOrEmpty(order.DeliveryName) ? order.DeliveryName : !string.IsNullOrWhiteSpace(order.CustomerName) ? order.CustomerName : user?.Name ?? ""; AddChildXmlNode(orderNode, "OrderDeliveryName", deliveryName); diff --git a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/ProductInfoXmlGenerator.cs b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/ProductInfoXmlGenerator.cs index a450f1c..65da720 100644 --- a/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/ProductInfoXmlGenerator.cs +++ b/src/Dynamicweb.Ecommerce.DynamicwebLiveIntegration/XmlGenerators/ProductInfoXmlGenerator.cs @@ -34,7 +34,9 @@ internal string GenerateProductInfoXml(Settings currentSettings, List