using LightlessSync.API.Routes; using LightlessSyncShared.Services; using LightlessSyncShared.Utils; using LightlessSyncShared.Utils.Configuration; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Caching.Memory; namespace LightlessSyncStaticFilesServer.Controllers; [Route(LightlessFiles.Speedtest)] public class SpeedTestController : ControllerBase { private readonly IMemoryCache _memoryCache; private readonly IConfigurationService _configurationService; private const string RandomByteDataName = "SpeedTestRandomByteData"; private static readonly SemaphoreSlim _speedtestSemaphore = new(10, 10); public SpeedTestController(ILogger logger, IMemoryCache memoryCache, IConfigurationService configurationService) : base(logger) { _memoryCache = memoryCache; _configurationService = configurationService; } [HttpGet(LightlessFiles.Speedtest_Run)] public async Task DownloadTest(CancellationToken cancellationToken) { var user = HttpContext.User.Claims.First(f => string.Equals(f.Type, LightlessClaimTypes.Uid, StringComparison.Ordinal)).Value; var speedtestLimit = _configurationService.GetValueOrDefault(nameof(StaticFilesServerConfiguration.SpeedTestHoursRateLimit), 0.5); if (_memoryCache.TryGetValue(user, out var value)) { var hoursRemaining = value.Subtract(DateTime.UtcNow).TotalHours; return StatusCode(429, $"Can perform speedtest every {speedtestLimit} hours. {hoursRemaining:F2} hours remain."); } await _speedtestSemaphore.WaitAsync(cancellationToken); try { var expiry = DateTime.UtcNow.Add(TimeSpan.FromHours(speedtestLimit)); _memoryCache.Set(user, expiry, TimeSpan.FromHours(speedtestLimit)); var randomByteData = _memoryCache.GetOrCreate(RandomByteDataName, (entry) => { byte[] data = new byte[100 * 1024 * 1024]; new Random().NextBytes(data); return data; }); return File(randomByteData, "application/octet-stream", "speedtest.dat"); } catch (OperationCanceledException) { return StatusCode(499, "Cancelled"); } finally { _speedtestSemaphore.Release(); } } }