Files
TuneRipper/TuneRipper.Api/Services/SessionService.cs
T
Michal Vrba 879fbd4a53 Lazily create session directories on first MP3 save
Session directories are now created only when the first MP3 file is saved, not at session creation. Added EnsureSessionDirectoryExists to ISessionService and SessionService to handle this logic. Updated VideoService to call this method before saving files. Cleanup now logs when no directory exists for expired sessions.
2025-12-14 16:39:00 +01:00

159 lines
5.4 KiB
C#

using System.Collections.Concurrent;
namespace TuneRipper.Api.Services;
public class SessionService : ISessionService
{
private readonly IWebHostEnvironment _environment;
private readonly ILogger<SessionService> _logger;
private readonly ConcurrentDictionary<string, DateTime> _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<SessionService> 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)
);
}
/// <summary>
/// Creates a new session with a unique ID.
/// Note: The session directory is NOT created at this point - it will be created
/// lazily when the first MP3 file is saved using EnsureSessionDirectoryExists().
/// </summary>
/// <returns>The unique session ID</returns>
public string CreateSession()
{
var sessionId = Guid.NewGuid().ToString("N")[..16]; // Use first 16 characters for shorter IDs
_sessions[sessionId] = DateTime.UtcNow;
// Don't create directory here - it will be created when first MP3 is saved
_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 if it exists
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);
}
}
else
{
_logger.LogDebug("No directory to clean up for expired session: {SessionId} (no MP3 files were downloaded)", 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);
}
/// <summary>
/// Ensures that the session directory exists, creating it if necessary.
/// This method should be called just before saving the first MP3 file in a session.
/// </summary>
/// <param name="sessionId">The session ID</param>
/// <exception cref="ArgumentException">Thrown when sessionId is null or empty</exception>
/// <exception cref="InvalidOperationException">Thrown when session is invalid or expired</exception>
public void EnsureSessionDirectoryExists(string sessionId)
{
if (string.IsNullOrWhiteSpace(sessionId))
{
throw new ArgumentException("Session ID cannot be null or empty", nameof(sessionId));
}
if (!IsValidSession(sessionId))
{
throw new InvalidOperationException($"Invalid session ID: {sessionId}");
}
var sessionPath = GetSessionDownloadsPath(sessionId);
if (!Directory.Exists(sessionPath))
{
Directory.CreateDirectory(sessionPath);
_logger.LogInformation("Created session directory for session: {SessionId} at {Path}", sessionId, sessionPath);
}
}
public void Dispose()
{
_cleanupTimer?.Dispose();
}
}