You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
322 lines
10 KiB
322 lines
10 KiB
using System.Collections; |
|
using System.ComponentModel; |
|
using System.Text.Json.Serialization; |
|
using JetBrains.Annotations; |
|
using StabilityMatrix.Core.Converters.Json; |
|
|
|
namespace StabilityMatrix.Core.Models.FileInterfaces; |
|
|
|
[PublicAPI] |
|
[Localizable(false)] |
|
[JsonConverter(typeof(StringJsonConverter<DirectoryPath>))] |
|
public class DirectoryPath : FileSystemPath, IPathObject, IEnumerable<FileSystemPath> |
|
{ |
|
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); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets a value indicating whether the directory exists. |
|
/// </summary> |
|
[JsonIgnore] |
|
public bool Exists => Info.Exists; |
|
|
|
/// <inheritdoc/> |
|
[JsonIgnore] |
|
public string Name => Info.Name; |
|
|
|
/// <summary> |
|
/// Get the parent directory. |
|
/// </summary> |
|
[JsonIgnore] |
|
public DirectoryPath? Parent => Info.Parent == null ? null : new DirectoryPath(Info.Parent); |
|
|
|
public DirectoryPath([Localizable(false)] 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([Localizable(false)] params string[] paths) |
|
: base(paths) { } |
|
|
|
public DirectoryPath RelativeTo(DirectoryPath path) |
|
{ |
|
return new DirectoryPath(Path.GetRelativePath(path.FullPath, FullPath)); |
|
} |
|
|
|
/// <inheritdoc /> |
|
public long GetSize() |
|
{ |
|
Info.Refresh(); |
|
return Info.EnumerateFiles("*", SearchOption.AllDirectories).Sum(file => file.Length); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the size of the directory. |
|
/// </summary> |
|
/// <param name="includeSymbolicLinks"> |
|
/// Whether to include files and subdirectories that are symbolic links / reparse points. |
|
/// </param> |
|
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; |
|
} |
|
|
|
/// <summary> |
|
/// Gets the size of the directory asynchronously. |
|
/// </summary> |
|
/// <param name="includeSymbolicLinks"> |
|
/// Whether to include files and subdirectories that are symbolic links / reparse points. |
|
/// </param> |
|
public Task<long> GetSizeAsync(bool includeSymbolicLinks) |
|
{ |
|
return Task.Run(() => GetSize(includeSymbolicLinks)); |
|
} |
|
|
|
/// <summary> |
|
/// Creates the directory. |
|
/// </summary> |
|
public void Create() => Directory.CreateDirectory(FullPath); |
|
|
|
/// <summary> |
|
/// Deletes the directory. |
|
/// </summary> |
|
public void Delete() => Info.Delete(); |
|
|
|
/// <summary> Deletes the directory asynchronously. </summary> |
|
public Task DeleteAsync() => Task.Run(Delete); |
|
|
|
/// <summary> |
|
/// Deletes the directory. |
|
/// </summary> |
|
/// <param name="recursive">Whether to delete subdirectories and files.</param> |
|
public void Delete(bool recursive) => Info.Delete(recursive); |
|
|
|
/// <summary> |
|
/// Deletes the directory asynchronously. |
|
/// </summary> |
|
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); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Move the directory to a destination path. |
|
/// </summary> |
|
public DirectoryPath MoveTo(DirectoryPath destinationDir) |
|
{ |
|
Info.MoveTo(destinationDir.FullPath); |
|
// Return the new path |
|
return destinationDir; |
|
} |
|
|
|
/// <summary> |
|
/// Move the file to a target path. |
|
/// </summary> |
|
public async Task<DirectoryPath> MoveToAsync(DirectoryPath destinationDir) |
|
{ |
|
await Task.Run(() => Info.MoveTo(destinationDir.FullPath)).ConfigureAwait(false); |
|
// Return the new path |
|
return destinationDir; |
|
} |
|
|
|
/// <summary> |
|
/// Move the directory to a destination path as a subfolder with the current name. |
|
/// </summary> |
|
public async Task<DirectoryPath> MoveToDirectoryAsync(DirectoryPath destinationParentDir) |
|
{ |
|
await Task.Run(() => Info.MoveTo(destinationParentDir.JoinDir(Name))).ConfigureAwait(false); |
|
// Return the new path |
|
return destinationParentDir.JoinDir(this); |
|
} |
|
|
|
/// <summary> |
|
/// Join with other paths to form a new directory path. |
|
/// </summary> |
|
public DirectoryPath JoinDir([Localizable(false)] params DirectoryPath[] paths) => |
|
new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray()))); |
|
|
|
/// <summary> |
|
/// Join with other paths to form a new file path. |
|
/// </summary> |
|
public FilePath JoinFile([Localizable(false)] params FilePath[] paths) => |
|
new(Path.Combine(FullPath, Path.Combine(paths.Select(path => path.FullPath).ToArray()))); |
|
|
|
/// <summary> |
|
/// Returns an enumerable collection of files that matches |
|
/// a specified search pattern and search subdirectory option. |
|
/// </summary> |
|
public IEnumerable<FilePath> EnumerateFiles( |
|
string searchPattern = "*", |
|
SearchOption searchOption = SearchOption.TopDirectoryOnly |
|
) => Info.EnumerateFiles(searchPattern, searchOption).Select(file => new FilePath(file)); |
|
|
|
/// <summary> |
|
/// Returns an enumerable collection of directories that matches |
|
/// a specified search pattern and search subdirectory option. |
|
/// </summary> |
|
public IEnumerable<DirectoryPath> EnumerateDirectories( |
|
string searchPattern = "*", |
|
SearchOption searchOption = SearchOption.TopDirectoryOnly |
|
) => |
|
Info.EnumerateDirectories(searchPattern, searchOption) |
|
.Select(directory => new DirectoryPath(directory)); |
|
|
|
/// <summary> |
|
/// Return a new <see cref="DirectoryPath"/> with the given file name. |
|
/// </summary> |
|
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; |
|
|
|
/// <inheritdoc /> |
|
public IEnumerator<FileSystemPath> GetEnumerator() |
|
{ |
|
return Info.EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly) |
|
.Select<FileSystemInfo, FileSystemPath>( |
|
fsInfo => |
|
fsInfo switch |
|
{ |
|
FileInfo file => new FilePath(file), |
|
DirectoryInfo directory => new DirectoryPath(directory), |
|
_ => throw new InvalidOperationException("Unknown file system info type") |
|
} |
|
) |
|
.GetEnumerator(); |
|
} |
|
|
|
/// <inheritdoc /> |
|
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, [Localizable(false)] 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([Localizable(false)] 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); |
|
}
|
|
|