using System.Collections; using System.Text.Json.Serialization; using JetBrains.Annotations; using StabilityMatrix.Core.Converters.Json; namespace StabilityMatrix.Core.Models.FileInterfaces; [PublicAPI] [JsonConverter(typeof(StringJsonConverter))] public class DirectoryPath : FileSystemPath, IPathObject, IEnumerable { private DirectoryInfo? info; [JsonIgnore] public DirectoryInfo Info => info ??= new DirectoryInfo(FullPath); [JsonIgnore] FileSystemInfo IPathObject.Info => Info; [JsonIgnore] public bool IsSymbolicLink { get { Info.Refresh(); return Info.Exists && Info.Attributes.HasFlag(FileAttributes.ReparsePoint); } } /// /// Gets a value indicating whether the directory exists. /// [JsonIgnore] public bool Exists => Info.Exists; /// [JsonIgnore] public string Name => Info.Name; /// /// Get the parent directory. /// [JsonIgnore] public DirectoryPath? Parent => Info.Parent == null ? null : new DirectoryPath(Info.Parent); public DirectoryPath(string path) : base(path) { } public DirectoryPath(FileSystemPath path) : base(path) { } public DirectoryPath(DirectoryInfo info) : base(info.FullName) { // Additionally set the info field this.info = info; } public DirectoryPath(params string[] paths) : base(paths) { } public DirectoryPath RelativeTo(DirectoryPath path) { return new DirectoryPath(Path.GetRelativePath(path.FullPath, FullPath)); } /// public long GetSize() { Info.Refresh(); return Info.EnumerateFiles("*", SearchOption.AllDirectories).Sum(file => file.Length); } /// /// Gets the size of the directory. /// /// /// Whether to include files and subdirectories that are symbolic links / reparse points. /// public long GetSize(bool includeSymbolicLinks) { if (includeSymbolicLinks) return GetSize(); Info.Refresh(); var files = Info.GetFiles() .Where(file => !file.Attributes.HasFlag(FileAttributes.ReparsePoint)) .Sum(file => file.Length); var subDirs = Info.GetDirectories() .Where(dir => !dir.Attributes.HasFlag(FileAttributes.ReparsePoint)) .Sum(dir => dir.EnumerateFiles("*", SearchOption.AllDirectories).Sum(file => file.Length)); return files + subDirs; } /// /// Gets the size of the directory asynchronously. /// /// /// Whether to include files and subdirectories that are symbolic links / reparse points. /// public Task GetSizeAsync(bool includeSymbolicLinks) { return Task.Run(() => GetSize(includeSymbolicLinks)); } /// /// Creates the directory. /// public void Create() => Directory.CreateDirectory(FullPath); /// /// Deletes the directory. /// public void Delete() => Info.Delete(); /// Deletes the directory asynchronously. public Task DeleteAsync() => Task.Run(Delete); /// /// Deletes the directory. /// /// Whether to delete subdirectories and files. public void Delete(bool recursive) => Info.Delete(recursive); /// /// Deletes the directory asynchronously. /// public Task DeleteAsync(bool recursive) => Task.Run(() => Delete(recursive)); void IPathObject.Delete() => Info.Delete(true); Task IPathObject.DeleteAsync() => DeleteAsync(true); private void ThrowIfNotExists() { if (!Exists) { throw new DirectoryNotFoundException($"Directory not found: {FullPath}"); } } public void CopyTo(DirectoryPath destinationDir, bool recursive = true) { ThrowIfNotExists(); // Cache directories before we start copying var dirs = EnumerateDirectories().ToList(); destinationDir.Create(); // Get the files in the source directory and copy to the destination directory foreach (var file in EnumerateFiles()) { var targetFilePath = destinationDir.JoinFile(file.Name); file.CopyTo(targetFilePath); } // If recursive and copying subdirectories, recursively call this method if (recursive) { foreach (var subDir in dirs) { var targetDirectory = destinationDir.JoinDir(subDir.Name); subDir.CopyTo(targetDirectory); } } } public async Task CopyToAsync(DirectoryPath destinationDir, bool recursive = true) { ThrowIfNotExists(); // Cache directories before we start copying var dirs = EnumerateDirectories().ToList(); destinationDir.Create(); // Get the files in the source directory and copy to the destination directory foreach (var file in EnumerateFiles()) { var targetFilePath = destinationDir.JoinFile(file.Name); await file.CopyToAsync(targetFilePath).ConfigureAwait(false); } // If recursive and copying subdirectories, recursively call this method if (recursive) { foreach (var subDir in dirs) { var targetDirectory = destinationDir.JoinDir(subDir.Name); await subDir.CopyToAsync(targetDirectory).ConfigureAwait(false); } } } /// /// Move the directory to a destination path. /// public DirectoryPath MoveTo(DirectoryPath destinationDir) { Info.MoveTo(destinationDir.FullPath); // Return the new path return destinationDir; } /// /// Move the file to a target path. /// public async Task MoveToAsync(DirectoryPath destinationDir) { await Task.Run(() => Info.MoveTo(destinationDir.FullPath)).ConfigureAwait(false); // Return the new path return destinationDir; } /// /// Move the directory to a destination path as a subfolder with the current name. /// public async Task MoveToDirectoryAsync(DirectoryPath destinationParentDir) { await Task.Run(() => Info.MoveTo(destinationParentDir.JoinDir(Name))).ConfigureAwait(false); // Return the new path return destinationParentDir.JoinDir(this); } /// /// Join with other paths to form a new directory path. /// public DirectoryPath JoinDir(params DirectoryPath[] paths) => new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray()))); /// /// Join with other paths to form a new file path. /// public FilePath JoinFile(params FilePath[] paths) => new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray()))); /// /// Returns an enumerable collection of files that matches /// a specified search pattern and search subdirectory option. /// public IEnumerable EnumerateFiles( string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly ) => Info.EnumerateFiles(searchPattern, searchOption).Select(file => new FilePath(file)); /// /// Returns an enumerable collection of directories that matches /// a specified search pattern and search subdirectory option. /// public IEnumerable EnumerateDirectories( string searchPattern = "*", SearchOption searchOption = SearchOption.TopDirectoryOnly ) => Info.EnumerateDirectories(searchPattern, searchOption) .Select(directory => new DirectoryPath(directory)); /// /// Return a new with the given file name. /// public DirectoryPath WithName(string directoryName) { if (Path.GetDirectoryName(FullPath) is { } directory && !string.IsNullOrWhiteSpace(directory)) { return new DirectoryPath(directory, directoryName); } return new DirectoryPath(directoryName); } public override string ToString() => FullPath; /// public IEnumerator GetEnumerator() { return Info.EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly) .Select( fsInfo => fsInfo switch { FileInfo file => new FilePath(file), DirectoryInfo directory => new DirectoryPath(directory), _ => throw new InvalidOperationException("Unknown file system info type") } ) .GetEnumerator(); } /// IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } // DirectoryPath + DirectoryPath = DirectoryPath public static DirectoryPath operator +(DirectoryPath path, DirectoryPath other) => new(Path.Combine(path, other.FullPath)); // DirectoryPath + FilePath = FilePath public static FilePath operator +(DirectoryPath path, FilePath other) => new(Path.Combine(path, other.FullPath)); // DirectoryPath + FileInfo = FilePath public static FilePath operator +(DirectoryPath path, FileInfo other) => new(Path.Combine(path, other.FullName)); // DirectoryPath + string = string public static string operator +(DirectoryPath path, string other) => Path.Combine(path, other); // Implicit conversions to and from string public static implicit operator string(DirectoryPath path) => path.FullPath; public static implicit operator DirectoryPath(string path) => new(path); // Implicit conversions to and from DirectoryInfo public static implicit operator DirectoryInfo(DirectoryPath path) => path.Info; public static implicit operator DirectoryPath(DirectoryInfo path) => new(path); }