using IOFile = System.IO.File;
namespace octo_fiesta.Services.Common;
///
/// Helper class for path building and sanitization.
/// Provides utilities for creating safe file and folder paths for downloaded music files.
///
public static class PathHelper
{
///
/// Builds the output path for a downloaded track following the Artist/Album/Track structure.
///
/// Base download directory path.
/// Artist name (will be sanitized).
/// Album name (will be sanitized).
/// Track title (will be sanitized).
/// Optional track number for prefix.
/// File extension (e.g., ".flac", ".mp3").
/// Full path for the track file.
public static string BuildTrackPath(string downloadPath, string artist, string album, string title, int? trackNumber, string extension)
{
var safeArtist = SanitizeFolderName(artist);
var safeAlbum = SanitizeFolderName(album);
var safeTitle = SanitizeFileName(title);
var artistFolder = Path.Combine(downloadPath, safeArtist);
var albumFolder = Path.Combine(artistFolder, safeAlbum);
var trackPrefix = trackNumber.HasValue ? $"{trackNumber:D2} - " : "";
var fileName = $"{trackPrefix}{safeTitle}{extension}";
return Path.Combine(albumFolder, fileName);
}
///
/// Sanitizes a file name by removing invalid characters.
///
/// Original file name.
/// Sanitized file name safe for all file systems.
public static string SanitizeFileName(string fileName)
{
if (string.IsNullOrWhiteSpace(fileName))
{
return "Unknown";
}
var invalidChars = Path.GetInvalidFileNameChars();
var sanitized = new string(fileName
.Select(c => invalidChars.Contains(c) ? '_' : c)
.ToArray());
if (sanitized.Length > 100)
{
sanitized = sanitized[..100];
}
return sanitized.Trim();
}
///
/// Sanitizes a folder name by removing invalid path characters.
///
/// Original folder name.
/// Sanitized folder name safe for all file systems.
public static string SanitizeFolderName(string folderName)
{
if (string.IsNullOrWhiteSpace(folderName))
{
return "Unknown";
}
var invalidChars = Path.GetInvalidFileNameChars()
.Concat(Path.GetInvalidPathChars())
.Distinct()
.ToArray();
var sanitized = new string(folderName
.Select(c => invalidChars.Contains(c) ? '_' : c)
.ToArray());
// Remove leading/trailing dots and spaces (Windows folder restrictions)
sanitized = sanitized.Trim().TrimEnd('.');
if (sanitized.Length > 100)
{
sanitized = sanitized[..100].TrimEnd('.');
}
// Ensure we have a valid name
if (string.IsNullOrWhiteSpace(sanitized))
{
return "Unknown";
}
return sanitized;
}
///
/// Resolves a unique file path by appending a counter if the file already exists.
///
/// Desired file path.
/// Unique file path that does not exist yet.
public static string ResolveUniquePath(string basePath)
{
if (!IOFile.Exists(basePath))
{
return basePath;
}
var directory = Path.GetDirectoryName(basePath)!;
var extension = Path.GetExtension(basePath);
var fileNameWithoutExt = Path.GetFileNameWithoutExtension(basePath);
var counter = 1;
string uniquePath;
do
{
uniquePath = Path.Combine(directory, $"{fileNameWithoutExt} ({counter}){extension}");
counter++;
} while (IOFile.Exists(uniquePath));
return uniquePath;
}
}