Refactor downloads to use job-specific subdirectories

Organize downloaded files into per-job subdirectories under "downloads" instead of a flat structure. Update DownloadController and VideoService to read, write, and delete files within these job-specific directories. Add helper methods for directory management and improve logging. Update download/delete endpoints and result URLs to reflect the new structure. Clean up empty job directories after file deletion.
This commit is contained in:
Michal Vrba
2025-12-14 11:04:46 +01:00
parent 7c99721174
commit 2d2e855202
14 changed files with 1073 additions and 66 deletions
+12 -10
View File
@@ -13,7 +13,7 @@ public interface IVideoService
Task<YouTubeVideoDetails?> GetVideoDetailsAsync(string url);
bool IsValidYouTubeUrl(string url);
string ExtractVideoId(string url);
Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, Action<int, string> progressCallback);
Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, string sessionId, Action<int, string> progressCallback);
}
public class VideoService : IVideoService
@@ -21,12 +21,14 @@ public class VideoService : IVideoService
private readonly ILogger<VideoService> _logger;
private readonly HttpClient _httpClient;
private readonly IWebHostEnvironment _environment;
private readonly ISessionService _sessionService;
public VideoService(ILogger<VideoService> logger, IWebHostEnvironment environment, IHttpClientFactory httpClientFactory)
public VideoService(ILogger<VideoService> logger, IWebHostEnvironment environment, IHttpClientFactory httpClientFactory, ISessionService sessionService)
{
_logger = logger;
_httpClient = httpClientFactory.CreateClient();
_environment = environment;
_sessionService = sessionService;
}
public async Task<YouTubeVideoDetails?> GetVideoDetailsAsync(string url)
@@ -90,7 +92,7 @@ public class VideoService : IVideoService
}
}
public async Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, Action<int, string> progressCallback)
public async Task<string?> ProcessVideoWithProgressAsync(string url, string format, string jobId, string sessionId, Action<int, string> progressCallback)
{
try
{
@@ -101,8 +103,8 @@ public class VideoService : IVideoService
throw new FileNotFoundException($"yt-dlp.exe not found at {ytDlpPath}");
}
// Create downloads directory if it doesn't exist
var downloadDir = Path.Combine(_environment.WebRootPath, "downloads");
// Get session-specific download directory
var downloadDir = _sessionService.GetSessionDownloadsPath(sessionId);
Directory.CreateDirectory(downloadDir);
// Get video details first for better filename
@@ -113,7 +115,7 @@ public class VideoService : IVideoService
var customFileName = $"{jobId}_{safeTitle}";
var outputTemplate = Path.Combine(downloadDir, $"{customFileName}.%(ext)s");
_logger.LogInformation("Processing video with filename pattern: {FileName}", customFileName);
_logger.LogInformation("Processing video with filename pattern: {FileName} in session: {SessionId}", customFileName, sessionId);
// Build yt-dlp arguments
var arguments = new StringBuilder();
@@ -188,7 +190,7 @@ public class VideoService : IVideoService
throw new Exception($"yt-dlp failed with exit code {process.ExitCode}. Error: {error}");
}
// Find the downloaded file with our job ID pattern
// Find the downloaded file with our job ID pattern in the session directory
var downloadedFiles = Directory.GetFiles(downloadDir, $"{jobId}_*.*")
.Where(f => !f.EndsWith(".part"))
.OrderByDescending(System.IO.File.GetCreationTime)
@@ -196,13 +198,13 @@ public class VideoService : IVideoService
if (downloadedFiles.Length == 0)
{
throw new Exception($"No downloaded file found for job {jobId} after processing");
throw new Exception($"No downloaded file found for job {jobId} in session {sessionId} after processing");
}
var downloadedFile = downloadedFiles[0];
var finalFileName = Path.GetFileName(downloadedFile);
_logger.LogInformation("Successfully processed video. Job: {JobId}, File: {FileName}", jobId, finalFileName);
_logger.LogInformation("Successfully processed video. Job: {JobId}, Session: {SessionId}, File: {FileName}", jobId, sessionId, finalFileName);
progressCallback(100, "Processing completed successfully");
@@ -210,7 +212,7 @@ public class VideoService : IVideoService
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing video with yt-dlp for job {JobId}: {Url}", jobId, url);
_logger.LogError(ex, "Error processing video with yt-dlp for job {JobId} in session {SessionId}: {Url}", jobId, sessionId, url);
progressCallback(0, $"Processing failed: {ex.Message}");
throw;
}