using Microsoft.AspNetCore.Mvc; namespace TuneRipper.Api.Controllers; [ApiController] [Route("api/[controller]")] public class DownloadController : ControllerBase { private readonly IWebHostEnvironment _environment; private readonly ILogger _logger; public DownloadController(IWebHostEnvironment environment, ILogger logger) { _environment = environment; _logger = logger; } [HttpGet("{jobId}")] public IActionResult DownloadFile(string jobId) { if (string.IsNullOrWhiteSpace(jobId)) { return BadRequest("Job ID is required"); } try { var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads"); if (!Directory.Exists(downloadsDir)) { _logger.LogWarning("Downloads directory not found: {Path}", downloadsDir); return NotFound("Downloads directory not found"); } // Find files related to this job ID var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*") .Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp")) .OrderByDescending(System.IO.File.GetCreationTime) .ToArray(); if (jobFiles.Length == 0) { // Fallback: look for any audio files that might match the pattern var audioFiles = Directory.GetFiles(downloadsDir, "*.*") .Where(f => f.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".m4a", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".webm", StringComparison.OrdinalIgnoreCase)) .Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp")) .OrderByDescending(System.IO.File.GetCreationTime) .ToArray(); if (audioFiles.Length == 0) { _logger.LogWarning("No files found for job ID: {JobId}", jobId); return NotFound("File not found"); } jobFiles = audioFiles.Take(1).ToArray(); } var filePath = jobFiles[0]; var fileName = Path.GetFileName(filePath); if (!System.IO.File.Exists(filePath)) { _logger.LogWarning("File not found: {FilePath}", filePath); return NotFound("File not found"); } _logger.LogInformation("Serving file: {FileName} for job: {JobId}", fileName, jobId); // Determine content type based on file extension var contentType = Path.GetExtension(fileName).ToLowerInvariant() switch { ".mp3" => "audio/mpeg", ".m4a" => "audio/mp4", ".webm" => "audio/webm", ".wav" => "audio/wav", ".ogg" => "audio/ogg", _ => "application/octet-stream" }; // Create a safe download filename var safeFileName = fileName; if (fileName.Contains("_")) { // Try to create a more user-friendly filename var parts = fileName.Split('_'); if (parts.Length >= 2) { var extension = Path.GetExtension(fileName); safeFileName = $"TuneRipper_{parts[0]}{extension}"; } } var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); return File(fileStream, contentType, safeFileName); } catch (Exception ex) { _logger.LogError(ex, "Error downloading file for job ID: {JobId}", jobId); return StatusCode(500, $"Error downloading file: {ex.Message}"); } } [HttpGet("info/{jobId}")] public IActionResult GetDownloadInfo(string jobId) { if (string.IsNullOrWhiteSpace(jobId)) { return BadRequest("Job ID is required"); } try { var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads"); if (!Directory.Exists(downloadsDir)) { return NotFound("Downloads directory not found"); } // Find files related to this job ID var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*") .Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp")) .OrderByDescending(System.IO.File.GetCreationTime) .ToArray(); if (jobFiles.Length == 0) { return NotFound("File not found"); } var filePath = jobFiles[0]; var fileName = Path.GetFileName(filePath); var fileInfo = new FileInfo(filePath); // Return download metadata var info = new { jobId, fileName, fileSize = FormatFileSize(fileInfo.Length), fileSizeBytes = fileInfo.Length, format = Path.GetExtension(fileName).TrimStart('.').ToUpperInvariant(), createdAt = fileInfo.CreationTime, lastModified = fileInfo.LastWriteTime, downloadUrl = $"/api/download/{jobId}" }; return Ok(info); } catch (Exception ex) { _logger.LogError(ex, "Error getting download info for job ID: {JobId}", jobId); return StatusCode(500, $"Error getting download info: {ex.Message}"); } } [HttpDelete("{jobId}")] public IActionResult DeleteFile(string jobId) { if (string.IsNullOrWhiteSpace(jobId)) { return BadRequest("Job ID is required"); } try { var downloadsDir = Path.Combine(_environment.WebRootPath, "downloads"); if (!Directory.Exists(downloadsDir)) { return NotFound("Downloads directory not found"); } // Find and delete files related to this job ID var jobFiles = Directory.GetFiles(downloadsDir, $"*{jobId}*.*"); var deletedCount = 0; foreach (var filePath in jobFiles) { try { System.IO.File.Delete(filePath); deletedCount++; _logger.LogInformation("Deleted file: {FilePath}", filePath); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to delete file: {FilePath}", filePath); } } if (deletedCount == 0) { return NotFound("No files found to delete"); } return Ok(new { message = $"Deleted {deletedCount} file(s)", deletedCount }); } catch (Exception ex) { _logger.LogError(ex, "Error deleting files for job ID: {JobId}", jobId); return StatusCode(500, $"Error deleting files: {ex.Message}"); } } private static string FormatFileSize(long bytes) { string[] sizes = { "B", "KB", "MB", "GB" }; double len = bytes; int order = 0; while (len >= 1024 && order < sizes.Length - 1) { order++; len = len / 1024; } return $"{len:0.##} {sizes[order]}"; } }