604 lines
22 KiB
C#
604 lines
22 KiB
C#
using Microsoft.AspNetCore.Mvc;
|
|
using TuneRipper.Api.Services;
|
|
|
|
namespace TuneRipper.Api.Controllers;
|
|
|
|
[ApiController]
|
|
[Route("api/[controller]")]
|
|
public class DownloadController : ControllerBase
|
|
{
|
|
private readonly ILogger<DownloadController> _logger;
|
|
private readonly ISessionService _sessionService;
|
|
|
|
public DownloadController(ILogger<DownloadController> logger, ISessionService sessionService)
|
|
{
|
|
_logger = logger;
|
|
_sessionService = sessionService;
|
|
}
|
|
|
|
// New endpoint to download session ZIP file
|
|
[HttpGet("zip/{sessionId}")]
|
|
public IActionResult DownloadSessionZip(string sessionId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return BadRequest("Session ID is required");
|
|
}
|
|
|
|
if (!_sessionService.IsValidSession(sessionId))
|
|
{
|
|
return NotFound("Invalid or expired session");
|
|
}
|
|
|
|
try
|
|
{
|
|
var sessionDownloadsDir = _sessionService.GetSessionDownloadsPath(sessionId);
|
|
|
|
if (!Directory.Exists(sessionDownloadsDir))
|
|
{
|
|
_logger.LogWarning("Session downloads directory not found: {Path}", sessionDownloadsDir);
|
|
return NotFound("Session downloads directory not found");
|
|
}
|
|
|
|
var zipFileName = $"session_{sessionId}.zip";
|
|
var zipFilePath = Path.Combine(sessionDownloadsDir, zipFileName);
|
|
|
|
if (!System.IO.File.Exists(zipFilePath))
|
|
{
|
|
_logger.LogWarning("Session ZIP file not found: {ZipFile}", zipFilePath);
|
|
return NotFound("No ZIP file available for this session");
|
|
}
|
|
|
|
_logger.LogInformation("Serving session ZIP file: {ZipFile} for session: {SessionId}", zipFileName, sessionId);
|
|
|
|
var friendlyZipName = $"TuneRipper_Session_{sessionId.Substring(0, 8)}.zip";
|
|
|
|
var fileStream = new FileStream(zipFilePath, FileMode.Open, FileAccess.Read);
|
|
return File(fileStream, "application/zip", friendlyZipName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error downloading session ZIP for session: {SessionId}", sessionId);
|
|
return StatusCode(500, $"Error downloading session ZIP: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// New endpoint to get session ZIP info
|
|
[HttpGet("zip/info/{sessionId}")]
|
|
public IActionResult GetSessionZipInfo(string sessionId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId))
|
|
{
|
|
return BadRequest("Session ID is required");
|
|
}
|
|
|
|
if (!_sessionService.IsValidSession(sessionId))
|
|
{
|
|
return NotFound("Invalid or expired session");
|
|
}
|
|
|
|
try
|
|
{
|
|
var sessionDownloadsDir = _sessionService.GetSessionDownloadsPath(sessionId);
|
|
|
|
if (!Directory.Exists(sessionDownloadsDir))
|
|
{
|
|
return NotFound("Session downloads directory not found");
|
|
}
|
|
|
|
var zipFileName = $"session_{sessionId}.zip";
|
|
var zipFilePath = Path.Combine(sessionDownloadsDir, zipFileName);
|
|
|
|
if (!System.IO.File.Exists(zipFilePath))
|
|
{
|
|
return NotFound("No ZIP file available for this session");
|
|
}
|
|
|
|
var fileInfo = new FileInfo(zipFilePath);
|
|
var mp3Count = Directory.GetFiles(sessionDownloadsDir, "*.mp3").Length;
|
|
|
|
var info = new
|
|
{
|
|
sessionId,
|
|
zipFileName,
|
|
fileSize = FormatFileSize(fileInfo.Length),
|
|
fileSizeBytes = fileInfo.Length,
|
|
mp3FilesCount = mp3Count,
|
|
createdAt = fileInfo.CreationTime,
|
|
lastModified = fileInfo.LastWriteTime,
|
|
downloadUrl = $"/api/download/zip/{sessionId}"
|
|
};
|
|
|
|
return Ok(info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error getting session ZIP info for session: {SessionId}", sessionId);
|
|
return StatusCode(500, $"Error getting session ZIP info: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// New session-aware download endpoint
|
|
[HttpGet("{sessionId}/{jobId}")]
|
|
public IActionResult DownloadFileBySession(string sessionId, string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId) || string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Session ID and Job ID are required");
|
|
}
|
|
|
|
if (!_sessionService.IsValidSession(sessionId))
|
|
{
|
|
return NotFound("Invalid or expired session");
|
|
}
|
|
|
|
try
|
|
{
|
|
var sessionDownloadsDir = _sessionService.GetSessionDownloadsPath(sessionId);
|
|
|
|
if (!Directory.Exists(sessionDownloadsDir))
|
|
{
|
|
_logger.LogWarning("Session downloads directory not found: {Path}", sessionDownloadsDir);
|
|
return NotFound("Session downloads directory not found");
|
|
}
|
|
|
|
// Find files with the specific job ID pattern in the session directory
|
|
var jobFiles = Directory.GetFiles(sessionDownloadsDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (jobFiles.Length == 0)
|
|
{
|
|
_logger.LogWarning("No files found for job ID: {JobId} in session: {SessionId}", jobId, sessionId);
|
|
return NotFound("File not found for the specified job ID");
|
|
}
|
|
|
|
var filePath = jobFiles[0]; // Take the first (most recent) file
|
|
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} in session: {SessionId}", fileName, jobId, sessionId);
|
|
|
|
// 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 user-friendly download filename from our pattern
|
|
var downloadFileName = CreateUserFriendlyFileName(fileName);
|
|
|
|
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
|
return File(fileStream, contentType, downloadFileName);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error downloading file for job ID: {JobId} in session: {SessionId}", jobId, sessionId);
|
|
return StatusCode(500, $"Error downloading file: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Legacy endpoint for backward compatibility
|
|
[HttpGet("{jobId}")]
|
|
public IActionResult DownloadFile(string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Job ID is required");
|
|
}
|
|
|
|
try
|
|
{
|
|
// Check in the legacy downloads directory first
|
|
var downloadsDir = _sessionService.GetDownloadsRootPath();
|
|
|
|
if (Directory.Exists(downloadsDir))
|
|
{
|
|
var legacyFiles = Directory.GetFiles(downloadsDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (legacyFiles.Length > 0)
|
|
{
|
|
return ServeFile(legacyFiles[0], jobId, null);
|
|
}
|
|
}
|
|
|
|
// If not found in legacy location, search all session directories
|
|
var sessionDirs = Directory.GetDirectories(downloadsDir, "*", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (var sessionDir in sessionDirs)
|
|
{
|
|
var sessionId = Path.GetFileName(sessionDir);
|
|
if (!_sessionService.IsValidSession(sessionId)) continue;
|
|
|
|
var jobFiles = Directory.GetFiles(sessionDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (jobFiles.Length > 0)
|
|
{
|
|
return ServeFile(jobFiles[0], jobId, sessionId);
|
|
}
|
|
}
|
|
|
|
_logger.LogWarning("No files found for job ID: {JobId}", jobId);
|
|
return NotFound("File not found for the specified job ID");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error downloading file for job ID: {JobId}", jobId);
|
|
return StatusCode(500, $"Error downloading file: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private IActionResult ServeFile(string filePath, string jobId, string? sessionId)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
|
|
if (!System.IO.File.Exists(filePath))
|
|
{
|
|
_logger.LogWarning("File not found: {FilePath}", filePath);
|
|
return NotFound("File not found");
|
|
}
|
|
|
|
var sessionInfo = sessionId != null ? $" in session: {sessionId}" : "";
|
|
_logger.LogInformation("Serving file: {FileName} for job: {JobId}{SessionInfo}", fileName, jobId, sessionInfo);
|
|
|
|
// 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 user-friendly download filename from our pattern
|
|
var downloadFileName = CreateUserFriendlyFileName(fileName);
|
|
|
|
var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
|
|
return File(fileStream, contentType, downloadFileName);
|
|
}
|
|
|
|
private string CreateUserFriendlyFileName(string originalFileName)
|
|
{
|
|
try
|
|
{
|
|
// Parse our pattern: jobId_title.ext
|
|
var nameWithoutExt = Path.GetFileNameWithoutExtension(originalFileName);
|
|
var extension = Path.GetExtension(originalFileName);
|
|
|
|
// Split by underscore - first part is job ID, rest is title
|
|
var underscoreIndex = nameWithoutExt.IndexOf('_');
|
|
|
|
if (underscoreIndex > 0 && underscoreIndex < nameWithoutExt.Length - 1)
|
|
{
|
|
// Extract title part (everything after the first underscore)
|
|
var titlePart = nameWithoutExt.Substring(underscoreIndex + 1);
|
|
|
|
// Convert underscores back to spaces in title
|
|
var friendlyTitle = titlePart.Replace("_", " ");
|
|
|
|
// Create a clean filename: "Title.mp3"
|
|
if (friendlyTitle.Length > 0)
|
|
{
|
|
// Remove any duplicate spaces and clean up
|
|
friendlyTitle = System.Text.RegularExpressions.Regex.Replace(friendlyTitle, @"\s+", " ").Trim();
|
|
return $"{friendlyTitle}{extension}";
|
|
}
|
|
}
|
|
|
|
// Fallback to original filename if pattern doesn't match
|
|
return originalFileName;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// If anything goes wrong, return the original filename
|
|
return originalFileName;
|
|
}
|
|
}
|
|
|
|
[HttpGet("info/{sessionId}/{jobId}")]
|
|
public IActionResult GetDownloadInfoBySession(string sessionId, string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId) || string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Session ID and Job ID are required");
|
|
}
|
|
|
|
if (!_sessionService.IsValidSession(sessionId))
|
|
{
|
|
return NotFound("Invalid or expired session");
|
|
}
|
|
|
|
try
|
|
{
|
|
var sessionDownloadsDir = _sessionService.GetSessionDownloadsPath(sessionId);
|
|
|
|
if (!Directory.Exists(sessionDownloadsDir))
|
|
{
|
|
return NotFound("Session downloads directory not found");
|
|
}
|
|
|
|
// Find files with the specific job ID pattern in the session directory
|
|
var jobFiles = Directory.GetFiles(sessionDownloadsDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (jobFiles.Length == 0)
|
|
{
|
|
return NotFound("File not found for the specified job ID");
|
|
}
|
|
|
|
var filePath = jobFiles[0];
|
|
var fileName = Path.GetFileName(filePath);
|
|
var fileInfo = new FileInfo(filePath);
|
|
var friendlyFileName = CreateUserFriendlyFileName(fileName);
|
|
|
|
// Return download metadata
|
|
var info = new
|
|
{
|
|
jobId,
|
|
sessionId,
|
|
fileName,
|
|
friendlyFileName,
|
|
fileSize = FormatFileSize(fileInfo.Length),
|
|
fileSizeBytes = fileInfo.Length,
|
|
format = Path.GetExtension(fileName).TrimStart('.').ToUpperInvariant(),
|
|
createdAt = fileInfo.CreationTime,
|
|
lastModified = fileInfo.LastWriteTime,
|
|
downloadUrl = $"/api/download/{sessionId}/{jobId}"
|
|
};
|
|
|
|
return Ok(info);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error getting download info for job ID: {JobId} in session: {SessionId}", jobId, sessionId);
|
|
return StatusCode(500, $"Error getting download info: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Legacy info endpoint for backward compatibility
|
|
[HttpGet("info/{jobId}")]
|
|
public IActionResult GetDownloadInfo(string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Job ID is required");
|
|
}
|
|
|
|
try
|
|
{
|
|
var downloadsDir = _sessionService.GetDownloadsRootPath();
|
|
|
|
if (!Directory.Exists(downloadsDir))
|
|
{
|
|
return NotFound("Downloads directory not found");
|
|
}
|
|
|
|
// Check legacy location first
|
|
var legacyFiles = Directory.GetFiles(downloadsDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (legacyFiles.Length > 0)
|
|
{
|
|
return GetFileInfo(legacyFiles[0], jobId, null);
|
|
}
|
|
|
|
// Search session directories
|
|
var sessionDirs = Directory.GetDirectories(downloadsDir, "*", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (var sessionDir in sessionDirs)
|
|
{
|
|
var sessionId = Path.GetFileName(sessionDir);
|
|
if (!_sessionService.IsValidSession(sessionId)) continue;
|
|
|
|
var jobFiles = Directory.GetFiles(sessionDir, $"{jobId}_*.*")
|
|
.Where(f => !f.EndsWith(".part") && !f.EndsWith(".tmp"))
|
|
.OrderByDescending(System.IO.File.GetCreationTime)
|
|
.ToArray();
|
|
|
|
if (jobFiles.Length > 0)
|
|
{
|
|
return GetFileInfo(jobFiles[0], jobId, sessionId);
|
|
}
|
|
}
|
|
|
|
return NotFound("File not found for the specified job ID");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error getting download info for job ID: {JobId}", jobId);
|
|
return StatusCode(500, $"Error getting download info: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private IActionResult GetFileInfo(string filePath, string jobId, string? sessionId)
|
|
{
|
|
var fileName = Path.GetFileName(filePath);
|
|
var fileInfo = new FileInfo(filePath);
|
|
var friendlyFileName = CreateUserFriendlyFileName(fileName);
|
|
|
|
var downloadUrl = sessionId != null
|
|
? $"/api/download/{sessionId}/{jobId}"
|
|
: $"/api/download/{jobId}";
|
|
|
|
var info = new
|
|
{
|
|
jobId,
|
|
sessionId,
|
|
fileName,
|
|
friendlyFileName,
|
|
fileSize = FormatFileSize(fileInfo.Length),
|
|
fileSizeBytes = fileInfo.Length,
|
|
format = Path.GetExtension(fileName).TrimStart('.').ToUpperInvariant(),
|
|
createdAt = fileInfo.CreationTime,
|
|
lastModified = fileInfo.LastWriteTime,
|
|
downloadUrl
|
|
};
|
|
|
|
return Ok(info);
|
|
}
|
|
|
|
[HttpDelete("{sessionId}/{jobId}")]
|
|
public IActionResult DeleteFileBySession(string sessionId, string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sessionId) || string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Session ID and Job ID are required");
|
|
}
|
|
|
|
if (!_sessionService.IsValidSession(sessionId))
|
|
{
|
|
return NotFound("Invalid or expired session");
|
|
}
|
|
|
|
try
|
|
{
|
|
var sessionDownloadsDir = _sessionService.GetSessionDownloadsPath(sessionId);
|
|
|
|
if (!Directory.Exists(sessionDownloadsDir))
|
|
{
|
|
return NotFound("Session downloads directory not found");
|
|
}
|
|
|
|
// Find and delete files with the specific job ID pattern in the session directory
|
|
var jobFiles = Directory.GetFiles(sessionDownloadsDir, $"{jobId}_*.*")
|
|
.ToArray();
|
|
|
|
var deletedCount = 0;
|
|
|
|
foreach (var filePath in jobFiles)
|
|
{
|
|
try
|
|
{
|
|
System.IO.File.Delete(filePath);
|
|
deletedCount++;
|
|
_logger.LogInformation("Deleted file: {FilePath} for job: {JobId} in session: {SessionId}", filePath, jobId, sessionId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to delete file: {FilePath}", filePath);
|
|
}
|
|
}
|
|
|
|
if (deletedCount == 0)
|
|
{
|
|
return NotFound("No files found to delete for the specified job ID");
|
|
}
|
|
|
|
return Ok(new { message = $"Deleted {deletedCount} file(s) for job {jobId} in session {sessionId}", deletedCount, jobId, sessionId });
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error deleting files for job ID: {JobId} in session: {SessionId}", jobId, sessionId);
|
|
return StatusCode(500, $"Error deleting files: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
// Legacy delete endpoint for backward compatibility
|
|
[HttpDelete("{jobId}")]
|
|
public IActionResult DeleteFile(string jobId)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(jobId))
|
|
{
|
|
return BadRequest("Job ID is required");
|
|
}
|
|
|
|
try
|
|
{
|
|
var downloadsDir = _sessionService.GetDownloadsRootPath();
|
|
var deletedCount = 0;
|
|
|
|
if (!Directory.Exists(downloadsDir))
|
|
{
|
|
return NotFound("Downloads directory not found");
|
|
}
|
|
|
|
// Delete from legacy location
|
|
var legacyFiles = Directory.GetFiles(downloadsDir, $"{jobId}_*.*");
|
|
foreach (var filePath in legacyFiles)
|
|
{
|
|
try
|
|
{
|
|
System.IO.File.Delete(filePath);
|
|
deletedCount++;
|
|
_logger.LogInformation("Deleted legacy file: {FilePath} for job: {JobId}", filePath, jobId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to delete legacy file: {FilePath}", filePath);
|
|
}
|
|
}
|
|
|
|
// Delete from session directories
|
|
var sessionDirs = Directory.GetDirectories(downloadsDir, "*", SearchOption.TopDirectoryOnly);
|
|
|
|
foreach (var sessionDir in sessionDirs)
|
|
{
|
|
var sessionId = Path.GetFileName(sessionDir);
|
|
var jobFiles = Directory.GetFiles(sessionDir, $"{jobId}_*.*");
|
|
|
|
foreach (var filePath in jobFiles)
|
|
{
|
|
try
|
|
{
|
|
System.IO.File.Delete(filePath);
|
|
deletedCount++;
|
|
_logger.LogInformation("Deleted file: {FilePath} for job: {JobId} in session: {SessionId}", filePath, jobId, sessionId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "Failed to delete file: {FilePath}", filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (deletedCount == 0)
|
|
{
|
|
return NotFound("No files found to delete for the specified job ID");
|
|
}
|
|
|
|
return Ok(new { message = $"Deleted {deletedCount} file(s) for job {jobId}", deletedCount, jobId });
|
|
}
|
|
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]}";
|
|
}
|
|
}
|