diff --git a/ReplayBrowser/Helpers/ReplayHelper.cs b/ReplayBrowser/Helpers/ReplayHelper.cs index ba2bbe9..0d4e307 100644 --- a/ReplayBrowser/Helpers/ReplayHelper.cs +++ b/ReplayBrowser/Helpers/ReplayHelper.cs @@ -60,25 +60,28 @@ public async Task> GetMostRecentReplays(AuthenticationState state) /// Fetches a player profile from the database. /// /// Thrown when the account is private and the requestor is not the account owner or an admin. - public async Task GetPlayerProfile(Guid playerGuid, AuthenticationState authenticationState) + public async Task GetPlayerProfile(Guid playerGuid, AuthenticationState authenticationState, bool skipCache = false, bool skipPermsCheck = false) { var accountCaller = await _accountService.GetAccount(authenticationState); var accountRequested = _accountService.GetAccountSettings(playerGuid); - - if (accountRequested is { RedactInformation: true }) + + if (!skipPermsCheck) { - if (accountCaller == null || !accountCaller.IsAdmin) + if (accountRequested is { RedactInformation: true }) { - if (accountCaller?.Guid != playerGuid) + if (accountCaller == null || !accountCaller.IsAdmin) { - throw new UnauthorizedAccessException("The account you are trying to view is private. Contact the account owner and ask them to make their account public."); + if (accountCaller?.Guid != playerGuid) + { + throw new UnauthorizedAccessException("The account you are trying to view is private. Contact the account owner and ask them to make their account public."); + } } } } var cacheKey = $"player-{playerGuid}"; - if (_cache.TryGetValue(cacheKey, out CollectedPlayerData cachedPlayerData)) + if (_cache.TryGetValue(cacheKey, out CollectedPlayerData cachedPlayerData) && !skipCache) { return cachedPlayerData; } @@ -218,15 +221,18 @@ public async Task> GetMostRecentReplays(AuthenticationState state) { collectedPlayerData.IsWatched = accountCaller.SavedProfiles.Contains(playerGuid); } - - await _accountService.AddHistory(accountCaller, new HistoryEntry() + + if (!skipPermsCheck) { - Action = Enum.GetName(typeof(Action), Action.ProfileViewed) ?? "Unknown", - Time = DateTime.UtcNow, - Details = $"Player GUID: {playerGuid} Username: {collectedPlayerData.PlayerData.Username}" - }); + await _accountService.AddHistory(accountCaller, new HistoryEntry() + { + Action = Enum.GetName(typeof(Action), Action.ProfileViewed) ?? "Unknown", + Time = DateTime.UtcNow, + Details = $"Player GUID: {playerGuid} Username: {collectedPlayerData.PlayerData.Username}" + }); + } - _cache.Set(cacheKey, collectedPlayerData, TimeSpan.FromMinutes(20)); + _cache.Set(cacheKey, collectedPlayerData, TimeSpan.FromMinutes(60)); return collectedPlayerData; } @@ -513,7 +519,7 @@ public async Task SearchReplays(List searchItems, paginatedResults.Add((paginatedList, totalItems)); } - _cache.Set(cacheKey, paginatedResults, TimeSpan.FromMinutes(5)); + _cache.Set(cacheKey, paginatedResults, TimeSpan.FromMinutes(35)); stopWatch.Stop(); Log.Information("Search took " + stopWatch.ElapsedMilliseconds + "ms."); diff --git a/ReplayBrowser/Services/ProfilePregeneratorService.cs b/ReplayBrowser/Services/ProfilePregeneratorService.cs new file mode 100644 index 0000000..01d7889 --- /dev/null +++ b/ReplayBrowser/Services/ProfilePregeneratorService.cs @@ -0,0 +1,72 @@ +using System.Diagnostics; +using System.Security.Claims; +using Microsoft.AspNetCore.Components.Authorization; +using ReplayBrowser.Data; +using ReplayBrowser.Helpers; +using Serilog; + +namespace ReplayBrowser.Services; + +/// +/// Service that checks every watched profile for every user and pregenerates the profile if it is not already generated for better loading times. +/// +public class ProfilePregeneratorService : IHostedService, IDisposable, IAsyncDisposable +{ + private Timer? _timer = null; + private readonly IServiceScopeFactory _scopeFactory; + + public ProfilePregeneratorService(IServiceScopeFactory scopeFactory) + { + _scopeFactory = scopeFactory; + } + + public Task StartAsync(CancellationToken cancellationToken) + { + _timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromMinutes(30)); + return Task.CompletedTask; + } + + public Task StopAsync(CancellationToken cancellationToken) + { + _timer?.Change(Timeout.Infinite, 0); + return Task.CompletedTask; + } + + private async void DoWork(object? state) + { + var sw = new Stopwatch(); + Log.Information("Starting profile pregeneration."); + sw.Start(); + + using var scope = _scopeFactory.CreateScope(); + var dbContext = scope.ServiceProvider.GetRequiredService(); + var replayHelper = scope.ServiceProvider.GetRequiredService(); + var profilesToGenerate = new List(); + + dbContext.Accounts.Select(a => a.SavedProfiles).ToList().ForEach(profiles => + { + foreach (var profile in profiles.Where(profile => !profilesToGenerate.Contains(profile))) + { + profilesToGenerate.Add(profile); + } + }); + + foreach (var guid in profilesToGenerate) + { + await replayHelper.GetPlayerProfile(guid, new AuthenticationState(new ClaimsPrincipal()), true, true); + Log.Information("Pregenerated profile for {Guid}.", guid); + } + sw.Stop(); + Log.Information("Profile pregeneration finished in {ElapsedMilliseconds}ms.", sw.ElapsedMilliseconds); + } + + public void Dispose() + { + _timer?.Dispose(); + } + + public async ValueTask DisposeAsync() + { + if (_timer != null) await _timer.DisposeAsync(); + } +} \ No newline at end of file diff --git a/ReplayBrowser/Startup.cs b/ReplayBrowser/Startup.cs index 62a93f4..49bc31d 100644 --- a/ReplayBrowser/Startup.cs +++ b/ReplayBrowser/Startup.cs @@ -75,11 +75,13 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddHostedService>(); services.AddHostedService>(); services.AddHostedService>(); services.AddHostedService>(); + services.AddHostedService>(); services.AddScoped();