using Microsoft.EntityFrameworkCore; using SharedLibraryCore.Database.Models; using SharedLibraryCore.Dtos; using SharedLibraryCore.Interfaces; using SharedLibraryCore.QueryHelper; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Data.Abstractions; using Microsoft.Extensions.Logging; using ILogger = Microsoft.Extensions.Logging.ILogger; using Data.Models; namespace IW4MAdmin.Application.Misc { /// /// implementation of IMetaService /// used to add and retrieve runtime and persistent meta /// public class MetaService : IMetaService { private readonly IDictionary> _metaActions; private readonly IDatabaseContextFactory _contextFactory; private readonly ILogger _logger; public MetaService(ILogger logger, IDatabaseContextFactory contextFactory) { _logger = logger; _metaActions = new Dictionary>(); _contextFactory = contextFactory; } public async Task AddPersistentMeta(string metaKey, string metaValue, EFClient client, EFMeta linkedMeta = null) { // this seems to happen if the client disconnects before they've had time to authenticate and be added if (client.ClientId < 1) { return; } await using var ctx = _contextFactory.CreateContext(); var existingMeta = await ctx.EFMeta .Where(_meta => _meta.Key == metaKey) .Where(_meta => _meta.ClientId == client.ClientId) .FirstOrDefaultAsync(); if (existingMeta != null) { existingMeta.Value = metaValue; existingMeta.Updated = DateTime.UtcNow; existingMeta.LinkedMetaId = linkedMeta?.MetaId; } else { ctx.EFMeta.Add(new EFMeta() { ClientId = client.ClientId, Created = DateTime.UtcNow, Key = metaKey, Value = metaValue, LinkedMetaId = linkedMeta?.MetaId }); } await ctx.SaveChangesAsync(); } public async Task SetPersistentMeta(string metaKey, string metaValue, int clientId) { await AddPersistentMeta(metaKey, metaValue, new EFClient { ClientId = clientId }); } public async Task IncrementPersistentMeta(string metaKey, int incrementAmount, int clientId) { var existingMeta = await GetPersistentMeta(metaKey, new EFClient { ClientId = clientId }); if (!long.TryParse(existingMeta?.Value ?? "0", out var existingValue)) { existingValue = 0; } var newValue = existingValue + incrementAmount; await SetPersistentMeta(metaKey, newValue.ToString(), clientId); } public async Task DecrementPersistentMeta(string metaKey, int decrementAmount, int clientId) { await IncrementPersistentMeta(metaKey, -decrementAmount, clientId); } public async Task AddPersistentMeta(string metaKey, string metaValue) { await using var ctx = _contextFactory.CreateContext(); var existingMeta = await ctx.EFMeta .Where(meta => meta.Key == metaKey) .Where(meta => meta.ClientId == null) .ToListAsync(); var matchValues = existingMeta .Where(meta => meta.Value == metaValue) .ToArray(); if (matchValues.Any()) { foreach (var meta in matchValues) { _logger.LogDebug("Updating existing meta with key {key} and id {id}", meta.Key, meta.MetaId); meta.Value = metaValue; meta.Updated = DateTime.UtcNow; } await ctx.SaveChangesAsync(); } else { _logger.LogDebug("Adding new meta with key {key}", metaKey); ctx.EFMeta.Add(new EFMeta() { Created = DateTime.UtcNow, Key = metaKey, Value = metaValue }); await ctx.SaveChangesAsync(); } } public async Task RemovePersistentMeta(string metaKey, EFClient client) { await using var context = _contextFactory.CreateContext(); var existingMeta = await context.EFMeta .FirstOrDefaultAsync(meta => meta.Key == metaKey && meta.ClientId == client.ClientId); if (existingMeta == null) { _logger.LogDebug("No meta with key {key} found for client id {id}", metaKey, client.ClientId); return; } _logger.LogDebug("Removing meta for key {key} with id {id}", metaKey, existingMeta.MetaId); context.EFMeta.Remove(existingMeta); await context.SaveChangesAsync(); } public async Task RemovePersistentMeta(string metaKey, string metaValue = null) { await using var context = _contextFactory.CreateContext(enableTracking: false); var existingMeta = await context.EFMeta .Where(meta => meta.Key == metaKey) .Where(meta => meta.ClientId == null) .ToListAsync(); if (metaValue == null) { _logger.LogDebug("Removing all meta for key {key} with ids [{ids}] ", metaKey, string.Join(", ", existingMeta.Select(meta => meta.MetaId))); existingMeta.ForEach(meta => context.Remove(existingMeta)); await context.SaveChangesAsync(); return; } var foundMeta = existingMeta.FirstOrDefault(meta => meta.Value == metaValue); if (foundMeta != null) { _logger.LogDebug("Removing meta for key {key} with id {id}", metaKey, foundMeta.MetaId); context.Remove(foundMeta); await context.SaveChangesAsync(); } } public async Task GetPersistentMeta(string metaKey, EFClient client) { await using var ctx = _contextFactory.CreateContext(enableTracking: false); return await ctx.EFMeta .Where(_meta => _meta.Key == metaKey) .Where(_meta => _meta.ClientId == client.ClientId) .Select(_meta => new EFMeta() { MetaId = _meta.MetaId, Key = _meta.Key, ClientId = _meta.ClientId, Value = _meta.Value, LinkedMetaId = _meta.LinkedMetaId, LinkedMeta = _meta.LinkedMetaId != null ? new EFMeta() { MetaId = _meta.LinkedMeta.MetaId, Key = _meta.LinkedMeta.Key, Value = _meta.LinkedMeta.Value } : null }) .FirstOrDefaultAsync(); } public async Task> GetPersistentMeta(string metaKey) { await using var context = _contextFactory.CreateContext(enableTracking: false); return await context.EFMeta .Where(meta => meta.Key == metaKey) .Where(meta => meta.ClientId == null) .Select(meta => new EFMeta { MetaId = meta.MetaId, Key = meta.Key, ClientId = meta.ClientId, Value = meta.Value, }) .ToListAsync(); } public void AddRuntimeMeta(MetaType metaKey, Func>> metaAction) where T : PaginationRequest where V : IClientMeta { if (!_metaActions.ContainsKey(metaKey)) { _metaActions.Add(metaKey, new List() { metaAction }); } else { _metaActions[metaKey].Add(metaAction); } } public async Task> GetRuntimeMeta(ClientPaginationRequest request) { var metas = await Task.WhenAll(_metaActions.Where(kvp => kvp.Key != MetaType.Information) .Select(async kvp => await kvp.Value[0](request))); return metas.SelectMany(m => (IEnumerable)m) .OrderByDescending(m => m.When) .Take(request.Count) .ToList(); } public async Task> GetRuntimeMeta(ClientPaginationRequest request, MetaType metaType) where T : IClientMeta { if (metaType == MetaType.Information) { var allMeta = new List(); var completedMeta = await Task.WhenAll(_metaActions[metaType].Select(async individualMetaRegistration => (IEnumerable)await individualMetaRegistration(request))); allMeta.AddRange(completedMeta.SelectMany(meta => meta)); return ProcessInformationMeta(allMeta); } var meta = await _metaActions[metaType][0](request) as IEnumerable; return meta; } private static IEnumerable ProcessInformationMeta(IEnumerable meta) where T : IClientMeta { var table = new List>(); var metaWithColumn = meta .Where(_meta => _meta.Column != null); var columnGrouping = metaWithColumn .GroupBy(_meta => _meta.Column); var metaToSort = meta.Except(metaWithColumn).ToList(); foreach (var metaItem in columnGrouping) { table.Add(new List(metaItem)); } while (metaToSort.Count > 0) { var sortingMeta = metaToSort.First(); int indexOfSmallestColumn() { int index = 0; int smallestColumnSize = int.MaxValue; for (int i = 0; i < table.Count; i++) { if (table[i].Count < smallestColumnSize) { smallestColumnSize = table[i].Count; index = i; } } return index; } int columnIndex = indexOfSmallestColumn(); sortingMeta.Column = columnIndex; sortingMeta.Order = columnGrouping .First(_group => _group.Key == columnIndex) .Count(); table[columnIndex].Add(sortingMeta); metaToSort.Remove(sortingMeta); } return meta; } } }