using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json.Serialization; using StabilityMatrix.Core.Converters.Json; namespace StabilityMatrix.Core.Models.FileInterfaces; [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] [JsonConverter(typeof(StringJsonConverter))] public partial class FilePath : FileSystemPath, IPathObject { private FileInfo? _info; [JsonIgnore] public FileInfo Info => _info ??= new FileInfo(FullPath); [JsonIgnore] public bool IsSymbolicLink { get { Info.Refresh(); return Info.Attributes.HasFlag(FileAttributes.ReparsePoint); } } [JsonIgnore] public bool Exists => Info.Exists; [JsonIgnore] public string Name => Info.Name; [JsonIgnore] public string NameWithoutExtension => Path.GetFileNameWithoutExtension(Info.Name); /// [JsonIgnore] public string Extension => Info.Extension; /// /// Get the directory of the file. /// [JsonIgnore] public DirectoryPath? Directory { get { try { return Info.Directory == null ? null : new DirectoryPath(Info.Directory); } catch (DirectoryNotFoundException) { return null; } } } public FilePath(string path) : base(path) { } public FilePath(FileInfo fileInfo) : base(fileInfo.FullName) { _info = fileInfo; } public FilePath(FileSystemPath path) : base(path) { } public FilePath(params string[] paths) : base(paths) { } public long GetSize() { Info.Refresh(); return Info.Length; } public long GetSize(bool includeSymbolicLinks) { if (!includeSymbolicLinks && IsSymbolicLink) return 0; return GetSize(); } public Task GetSizeAsync(bool includeSymbolicLinks) { return Task.Run(() => GetSize(includeSymbolicLinks)); } /// Creates an empty file. public void Create() => File.Create(FullPath).Close(); /// Deletes the file public void Delete() => File.Delete(FullPath); /// Deletes the file asynchronously public Task DeleteAsync(CancellationToken ct = default) { return Task.Run(() => File.Delete(FullPath), ct); } // Methods specific to files /// Read text public string ReadAllText() => File.ReadAllText(FullPath); /// Read text asynchronously public Task ReadAllTextAsync(CancellationToken ct = default) { return File.ReadAllTextAsync(FullPath, ct); } /// Write text public void WriteAllText(string text, Encoding? encoding = null) => File.WriteAllText(FullPath, text, encoding ?? new UTF8Encoding(false)); /// Write text asynchronously public Task WriteAllTextAsync(string text, CancellationToken ct = default, Encoding? encoding = null) { return File.WriteAllTextAsync(FullPath, text, encoding ?? new UTF8Encoding(false), ct); } /// Read bytes public byte[] ReadAllBytes() => File.ReadAllBytes(FullPath); /// Read bytes asynchronously public Task ReadAllBytesAsync(CancellationToken ct = default) { return File.ReadAllBytesAsync(FullPath, ct); } /// Write bytes public void WriteAllBytes(byte[] bytes) => File.WriteAllBytes(FullPath, bytes); /// Write bytes asynchronously public Task WriteAllBytesAsync(byte[] bytes, CancellationToken ct = default) { return File.WriteAllBytesAsync(FullPath, bytes, ct); } /// /// Rename the file. /// public FilePath Rename(string fileName) { if (Path.GetDirectoryName(FullPath) is { } directory && !string.IsNullOrWhiteSpace(directory)) { var target = Path.Combine(directory, fileName); Info.MoveTo(target, true); return new FilePath(target); } throw new InvalidOperationException("Cannot rename a file path that is empty or has no directory"); } /// /// Move the file to a directory. /// public FilePath MoveTo(FilePath destinationFile) { Info.MoveTo(destinationFile.FullPath, true); // Return the new path return destinationFile; } /// /// Move the file to a directory. /// public async Task MoveToDirectoryAsync(DirectoryPath directory) { await Task.Run(() => Info.MoveTo(directory.JoinFile(Name), true)).ConfigureAwait(false); // Return the new path return directory.JoinFile(this); } /// /// Move the file to a target path. /// public async Task MoveToAsync(FilePath destinationFile) { await Task.Run(() => Info.MoveTo(destinationFile.FullPath)).ConfigureAwait(false); // Return the new path return destinationFile; } /// /// Move the file to a target path with auto increment if the file already exists. /// /// The new path, possibly with incremented file name public async Task MoveToWithIncrementAsync(FilePath destinationFile, int maxTries = 100) { await Task.Yield(); var targetFile = destinationFile; for (var i = 1; i < maxTries; i++) { if (!targetFile.Exists) { return await MoveToAsync(targetFile).ConfigureAwait(false); } targetFile = destinationFile.WithName( destinationFile.NameWithoutExtension + $" ({i})" + destinationFile.Extension ); } throw new IOException($"Could not move file to {destinationFile} because it already exists."); } /// /// Copy the file to a target path. /// public FilePath CopyTo(FilePath destinationFile, bool overwrite = false) { Info.CopyTo(destinationFile.FullPath, overwrite); // Return the new path return destinationFile; } /// /// Copy the file to a target path asynchronously. /// public async Task CopyToAsync(FilePath destinationFile, bool overwrite = false) { await using var sourceStream = Info.OpenRead(); await using var destinationStream = destinationFile.Info.OpenWrite(); await sourceStream.CopyToAsync(destinationStream).ConfigureAwait(false); // Return the new path return destinationFile; } // Implicit conversions to and from string public static implicit operator string(FilePath path) => path.FullPath; public static implicit operator FilePath(string path) => new(path); }