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
+54 -10
View File
@@ -1,7 +1,10 @@
@page "/"
@using Microsoft.AspNetCore.Components.Web
@using TuneRipper.Shared.Models
@using TuneRipper.Services
@inject IJSRuntime JSRuntime
@inject ITuneRipperApiService ApiService
@inject IClientSessionService SessionService
@inject ILogger<Home> Logger
<PageTitle>TuneRipper - YouTube to MP3 Downloader</PageTitle>
@@ -25,6 +28,14 @@
<img src="images/logo.png" alt="TuneRipper Logo" class="logo" />
<h1 class="app-title">TuneRipper</h1>
<p class="app-subtitle">Convert YouTube videos to MP3 instantly</p>
@* Session info display *@
@if (!string.IsNullOrWhiteSpace(currentSessionId))
{
<div class="session-info">
<small>Session: @currentSessionId.Substring(0, 8)...</small>
</div>
}
</div>
<div class="download-form">
@@ -161,6 +172,7 @@
private string progressMessage = "";
private List<DownloadItem> downloadLinks = new();
private YouTubeVideoDetails? currentVideoDetails = null;
private string currentSessionId = "";
public class DownloadItem
{
@@ -170,6 +182,22 @@
public DateTime CreatedAt { get; set; }
}
protected override async Task OnInitializedAsync()
{
try
{
// Initialize session on component load
currentSessionId = await SessionService.GetCurrentSessionIdAsync();
Logger.LogInformation("Initialized with session: {SessionId}", currentSessionId);
StateHasChanged();
}
catch (Exception ex)
{
Logger.LogError(ex, "Error initializing session");
// Don't block the UI if session initialization fails
}
}
private async Task DownloadMp3()
{
if (!string.IsNullOrWhiteSpace(youtubeUrl))
@@ -182,6 +210,12 @@
try
{
// Ensure we have a session
if (string.IsNullOrWhiteSpace(currentSessionId))
{
currentSessionId = await SessionService.GetCurrentSessionIdAsync();
}
// Step 1: Start processing (which includes video details fetching)
progressMessage = "Starting video processing...";
progressPercentage = 5;
@@ -191,7 +225,8 @@
{
Url = youtubeUrl,
Format = "mp3",
Quality = "128"
Quality = "128",
SessionId = currentSessionId // Include session ID in request
};
var processingResponse = await ApiService.StartProcessingAsync(request);
@@ -199,9 +234,18 @@
if (processingResponse?.Success == true)
{
var jobId = processingResponse.FileName; // This contains the job ID
var sessionId = processingResponse.SessionId ?? currentSessionId;
// Update current session ID if server returned a different one
if (!string.IsNullOrWhiteSpace(processingResponse.SessionId) &&
processingResponse.SessionId != currentSessionId)
{
currentSessionId = processingResponse.SessionId;
await SessionService.SetSessionIdAsync(currentSessionId);
}
// Step 2: Monitor processing status and get video details
await MonitorProcessingAndShowDetails(jobId);
await MonitorProcessingAndShowDetails(jobId, sessionId);
}
else
{
@@ -221,7 +265,7 @@
}
}
private async Task MonitorProcessingAndShowDetails(string jobId)
private async Task MonitorProcessingAndShowDetails(string jobId, string sessionId)
{
try
{
@@ -256,7 +300,7 @@
var result = await ApiService.GetProcessingResultAsync(jobId);
if (result?.Success == true)
{
await CompleteProcessing(result, jobId);
await CompleteProcessing(result, jobId, sessionId);
}
else
{
@@ -295,7 +339,7 @@
}
}
private async Task CompleteProcessing(ProcessVideoResponse result, string jobId)
private async Task CompleteProcessing(ProcessVideoResponse result, string jobId, string sessionId)
{
try
{
@@ -308,17 +352,17 @@
// Use the actual video title from API
string title = currentVideoDetails?.Title ?? result.VideoDetails?.Title ?? ExtractTitleFromUrl(youtubeUrl);
// Create the download URL - either from the result or construct it from the job ID
// Create the session-aware download URL
string downloadUrl;
if (!string.IsNullOrEmpty(result.DownloadUrl))
if (!string.IsNullOrEmpty(result.DownloadUrl) && result.DownloadUrl.Contains(sessionId))
{
// If the result contains a direct download URL, use it
// Server provided a session-aware download URL
downloadUrl = result.DownloadUrl.StartsWith("http") ? result.DownloadUrl : $"https://localhost:7160{result.DownloadUrl}";
}
else
{
// Fallback: construct download URL from job ID
downloadUrl = $"https://localhost:7160/api/download/{jobId}";
// Construct session-aware download URL
downloadUrl = $"https://localhost:7160/api/download/{sessionId}/{jobId}";
}
downloadLinks.Add(new DownloadItem