using System.Collections.Concurrent; namespace TuneRipper.Api.Services; public class SessionService : ISessionService { private readonly IWebHostEnvironment _environment; private readonly ILogger _logger; private readonly ConcurrentDictionary _sessions = new(); private readonly Timer _cleanupTimer; // Session expires after 24 hours of inactivity private readonly TimeSpan _sessionTimeout = TimeSpan.FromHours(24); public SessionService(IWebHostEnvironment environment, ILogger logger) { _environment = environment; _logger = logger; // Clean up expired sessions every hour _cleanupTimer = new Timer( callback: _ => CleanupExpiredSessions(), state: null, dueTime: TimeSpan.FromHours(1), period: TimeSpan.FromHours(1) ); } public string CreateSession() { var sessionId = Guid.NewGuid().ToString("N")[..16]; // Use first 16 characters for shorter IDs _sessions[sessionId] = DateTime.UtcNow; // Ensure session directory exists var sessionPath = GetSessionDownloadsPath(sessionId); Directory.CreateDirectory(sessionPath); _logger.LogInformation("Created new session: {SessionId}", sessionId); return sessionId; } public string GetOrCreateSession(string? sessionId) { if (!string.IsNullOrWhiteSpace(sessionId) && IsValidSession(sessionId)) { // Update last activity time _sessions[sessionId] = DateTime.UtcNow; return sessionId; } return CreateSession(); } public bool IsValidSession(string sessionId) { if (string.IsNullOrWhiteSpace(sessionId)) return false; if (_sessions.TryGetValue(sessionId, out var lastActivity)) { // Check if session has expired if (DateTime.UtcNow - lastActivity > _sessionTimeout) { _sessions.TryRemove(sessionId, out _); return false; } return true; } return false; } public void CleanupExpiredSessions() { try { var expiredSessions = _sessions .Where(kvp => DateTime.UtcNow - kvp.Value > _sessionTimeout) .Select(kvp => kvp.Key) .ToList(); foreach (var sessionId in expiredSessions) { if (_sessions.TryRemove(sessionId, out _)) { // Clean up session directory var sessionPath = GetSessionDownloadsPath(sessionId); if (Directory.Exists(sessionPath)) { try { Directory.Delete(sessionPath, recursive: true); _logger.LogInformation("Cleaned up expired session directory: {SessionId}", sessionId); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to delete expired session directory: {SessionId}", sessionId); } } } } if (expiredSessions.Count > 0) { _logger.LogInformation("Cleaned up {Count} expired sessions", expiredSessions.Count); } } catch (Exception ex) { _logger.LogError(ex, "Error during session cleanup"); } } public string GetSessionDownloadsPath(string sessionId) { var downloadsRoot = Path.Combine(_environment.WebRootPath, "downloads"); return Path.Combine(downloadsRoot, sessionId); } public void Dispose() { _cleanupTimer?.Dispose(); } }