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; } }