diff --git a/TuneRipper.Api/Services/ISessionService.cs b/TuneRipper.Api/Services/ISessionService.cs index 4f92364..1695b04 100644 --- a/TuneRipper.Api/Services/ISessionService.cs +++ b/TuneRipper.Api/Services/ISessionService.cs @@ -7,4 +7,5 @@ public interface ISessionService bool IsValidSession(string sessionId); void CleanupExpiredSessions(); string GetSessionDownloadsPath(string sessionId); + void EnsureSessionDirectoryExists(string sessionId); } \ No newline at end of file diff --git a/TuneRipper.Api/Services/SessionService.cs b/TuneRipper.Api/Services/SessionService.cs index 84257d0..6a99063 100644 --- a/TuneRipper.Api/Services/SessionService.cs +++ b/TuneRipper.Api/Services/SessionService.cs @@ -26,15 +26,18 @@ public class SessionService : ISessionService ); } + /// + /// 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(). + /// + /// The unique session ID 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); - + // Don't create directory here - it will be created when first MP3 is saved _logger.LogInformation("Created new session: {SessionId}", sessionId); return sessionId; } @@ -83,7 +86,7 @@ public class SessionService : ISessionService { if (_sessions.TryRemove(sessionId, out _)) { - // Clean up session directory + // Clean up session directory if it exists var sessionPath = GetSessionDownloadsPath(sessionId); if (Directory.Exists(sessionPath)) { @@ -97,6 +100,10 @@ public class SessionService : ISessionService _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); + } } } @@ -117,6 +124,34 @@ public class SessionService : ISessionService return Path.Combine(downloadsRoot, sessionId); } + /// + /// 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. + /// + /// The session ID + /// Thrown when sessionId is null or empty + /// Thrown when session is invalid or expired + 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(); diff --git a/TuneRipper.Api/Services/VideoService.cs b/TuneRipper.Api/Services/VideoService.cs index caba0ce..a2dd0a5 100644 --- a/TuneRipper.Api/Services/VideoService.cs +++ b/TuneRipper.Api/Services/VideoService.cs @@ -104,9 +104,11 @@ public class VideoService : IVideoService throw new FileNotFoundException($"yt-dlp.exe not found at {ytDlpPath}"); } - // Get session-specific download directory + // Ensure session directory exists only when we're about to save the first MP3 file + _sessionService.EnsureSessionDirectoryExists(sessionId); + + // Get session-specific download directory (now guaranteed to exist) var downloadDir = _sessionService.GetSessionDownloadsPath(sessionId); - Directory.CreateDirectory(downloadDir); // Get video details first for better filename var videoDetails = await GetVideoDetailsAsync(url);