// (c) Copyright Ascensio System SIA 2010-2022 // // This program is a free software product. // You can redistribute it and/or modify it under the terms // of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software // Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended // to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of // any third-party rights. // // This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty // of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see // the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html // // You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021. // // The interactive user interfaces in modified source and object code versions of the Program must // display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3. // // Pursuant to Section 7(b) of the License you must retain the original Product logo when // distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under // trademark law for use of our trademarks. // // All the Product's GUI elements, including illustrations and icon sets, as well as technical writing // content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0 // International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode using IsolationLevel = System.Data.IsolationLevel; namespace ASC.MessagingSystem.Data; [Singletone(Additional = typeof(MessagesRepositoryExtension))] public class MessagesRepository : IDisposable { private DateTime _lastSave = DateTime.UtcNow; private bool _timerStarted; private readonly TimeSpan _cacheTime; private readonly IDictionary _cache; private readonly IServiceScopeFactory _serviceScopeFactory; private readonly IMapper _mapper; private readonly ILogger _logger; private readonly Timer _timer; private readonly int _cacheLimit; public MessagesRepository(IServiceScopeFactory serviceScopeFactory, ILogger logger, IMapper mapper, IConfiguration configuration) { _cacheTime = TimeSpan.FromMinutes(1); _cache = new Dictionary(); _timerStarted = false; _logger = logger; _serviceScopeFactory = serviceScopeFactory; _timer = new Timer(FlushCache); _mapper = mapper; var minutes = configuration["messaging:CacheTimeFromMinutes"]; var limit = configuration["messaging:CacheLimit"]; _cacheTime = int.TryParse(minutes, out var cacheTime) ? TimeSpan.FromMinutes(cacheTime) : TimeSpan.FromMinutes(1); _cacheLimit = int.TryParse(limit, out var cacheLimit) ? cacheLimit : 100; } ~MessagesRepository() { FlushCache(true); } private bool ForseSave(EventMessage message) { // messages with action code < 2000 are related to login-history if ((int)message.Action < 2000) { return true; } return message.Action == MessageAction.UserSentPasswordChangeInstructions; } public int Add(EventMessage message) { if (ForseSave(message)) { var id = 0; if (!string.IsNullOrEmpty(message.UAHeader)) { try { MessageSettings.AddInfoMessage(message); } catch (Exception e) { _logger.ErrorWithException("Add " + message.Id, e); } } using var scope = _serviceScopeFactory.CreateScope(); using var ef = scope.ServiceProvider.GetService>().Get("messages"); if ((int)message.Action < 2000) { id = AddLoginEvent(message, ef); } else { id = AddAuditEvent(message, ef); } return id; } var now = DateTime.UtcNow; var key = string.Format("{0}|{1}|{2}|{3}", message.TenantId, message.UserId, message.Id, now.Ticks); lock (_cache) { _cache[key] = message; if (!_timerStarted) { _timer.Change(0, 100); _timerStarted = true; } } return 0; } private void FlushCache(object state) { FlushCache(false); } private void FlushCache(bool isDisposed = false) { List events = null; if (DateTime.UtcNow > _lastSave.Add(_cacheTime) || _cache.Count > _cacheLimit || isDisposed) { lock (_cache) { _timer.Change(-1, -1); _timerStarted = false; events = new List(_cache.Values); _cache.Clear(); _lastSave = DateTime.UtcNow; } } if (events == null) { return; } using var scope = _serviceScopeFactory.CreateScope(); using var ef = scope.ServiceProvider.GetService>().Get("messages"); using var tx = ef.Database.BeginTransaction(IsolationLevel.ReadUncommitted); var dict = new Dictionary(); foreach (var message in events) { if (!string.IsNullOrEmpty(message.UAHeader)) { try { MessageSettings.AddInfoMessage(message, dict); } catch (Exception e) { _logger.ErrorFlushCache(message.Id, e); } } if (!ForseSave(message)) { // messages with action code < 2000 are related to login-history if ((int)message.Action < 2000) { AddLoginEvent(message, ef); } else { AddAuditEvent(message, ef); } } } tx.Commit(); } private int AddLoginEvent(EventMessage message, MessagesContext dbContext) { var loginEvent = _mapper.Map(message); dbContext.LoginEvents.Add(loginEvent); dbContext.SaveChanges(); return loginEvent.Id; } private int AddAuditEvent(EventMessage message, MessagesContext dbContext) { var auditEvent = _mapper.Map(message); dbContext.AuditEvents.Add(auditEvent); dbContext.SaveChanges(); return auditEvent.Id; } public void Dispose() { if (_timer != null) { _timer.Dispose(); } } } public static class MessagesRepositoryExtension { public static void Register(DIHelper services) { services.TryAdd>(); services.TryAdd(); } }