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.
159 lines
5.4 KiB
C#
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();
|
|
}
|
|
} |