wabbajack/Wabbajack.Common/Paths.cs

859 lines
25 KiB
C#
Raw Normal View History

using System;
2020-03-25 23:15:19 +00:00
using System.Collections;
2020-03-23 12:57:18 +00:00
using System.Collections.Generic;
2020-04-04 04:02:53 +00:00
using System.Diagnostics.CodeAnalysis;
2020-03-23 12:57:18 +00:00
using System.IO;
using System.Linq;
2020-03-28 20:04:22 +00:00
using System.Net;
2020-03-23 12:57:18 +00:00
using System.Reflection;
using System.Runtime.InteropServices;
2020-03-23 12:57:18 +00:00
using System.Text;
using System.Threading.Tasks;
using Alphaleonis.Win32.Filesystem;
using Newtonsoft.Json;
using Directory = Alphaleonis.Win32.Filesystem.Directory;
2020-03-23 12:57:18 +00:00
using File = Alphaleonis.Win32.Filesystem.File;
using FileInfo = Alphaleonis.Win32.Filesystem.FileInfo;
using Path = Alphaleonis.Win32.Filesystem.Path;
namespace Wabbajack.Common
{
2020-03-24 12:21:19 +00:00
public interface IPath
2020-03-23 12:57:18 +00:00
{
2020-03-25 12:47:25 +00:00
/// <summary>
/// Get the final file name, for c:\bar\baz this is `baz` for c:\bar.zip this is `bar.zip`
/// for `bar.zip` this is `bar.zip`
/// </summary>
public RelativePath FileName { get; }
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
public struct AbsolutePath : IPath, IComparable<AbsolutePath>, IEquatable<AbsolutePath>
2020-03-23 12:57:18 +00:00
{
#region ObjectEquality
2020-03-25 12:47:25 +00:00
public bool Equals(AbsolutePath other)
2020-03-23 12:57:18 +00:00
{
return string.Equals(_path, other._path, StringComparison.InvariantCultureIgnoreCase);
2020-03-23 12:57:18 +00:00
}
public override bool Equals(object? obj)
2020-03-23 12:57:18 +00:00
{
return obj is AbsolutePath other && Equals(other);
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
#endregion
public override int GetHashCode()
{
return _path.GetHashCode(StringComparison.InvariantCultureIgnoreCase);
2020-03-23 12:57:18 +00:00
}
2020-03-27 03:10:23 +00:00
public override string ToString()
{
return _path;
}
private readonly string _nullable_path;
private string _path => _nullable_path ?? string.Empty;
2020-03-23 12:57:18 +00:00
2020-04-01 20:35:13 +00:00
public AbsolutePath(string path, bool skipValidation = false)
2020-03-23 12:57:18 +00:00
{
_nullable_path = path.Replace("/", "\\").TrimEnd('\\');
2020-03-25 12:47:25 +00:00
if (!skipValidation)
{
2020-03-23 12:57:18 +00:00
ValidateAbsolutePath();
2020-03-25 12:47:25 +00:00
}
2020-03-23 12:57:18 +00:00
}
public AbsolutePath(AbsolutePath path)
{
_nullable_path = path._path;
2020-03-23 12:57:18 +00:00
}
private void ValidateAbsolutePath()
{
2020-03-25 12:47:25 +00:00
if (Path.IsPathRooted(_path))
{
return;
}
2020-05-14 22:21:56 +00:00
throw new InvalidDataException($"Absolute path must be absolute, got {_path}");
2020-03-23 12:57:18 +00:00
}
2020-03-24 12:21:19 +00:00
public string Normalize()
{
return _path.Replace("/", "\\").TrimEnd('\\');
}
public Extension Extension => Extension.FromPath(_path);
2020-03-23 12:57:18 +00:00
public ValueTask<FileStream> OpenRead()
2020-03-23 12:57:18 +00:00
{
return OpenShared();
2020-03-23 12:57:18 +00:00
}
public ValueTask<FileStream> Create()
2020-03-23 12:57:18 +00:00
{
var path = _path;
2020-06-01 20:26:03 +00:00
return CircuitBreaker.WithAutoRetryAsync<FileStream, IOException>(async () => File.Open(path, FileMode.Create, FileAccess.ReadWrite));
2020-03-23 12:57:18 +00:00
}
public ValueTask<FileStream> OpenWrite()
2020-03-23 12:57:18 +00:00
{
var path = _path;
2020-06-01 20:26:03 +00:00
return CircuitBreaker.WithAutoRetryAsync<FileStream, IOException>(async () => File.OpenWrite(path));
2020-03-23 12:57:18 +00:00
}
public async Task WriteAllTextAsync(string text)
{
await using var fs = File.Create(_path);
await fs.WriteAsync(Encoding.UTF8.GetBytes(text));
}
public bool Exists => File.Exists(_path) || Directory.Exists(_path);
public bool IsFile => File.Exists(_path);
public bool IsDirectory => Directory.Exists(_path);
2020-03-28 04:33:26 +00:00
public async Task DeleteDirectory()
{
2020-03-25 12:47:25 +00:00
if (IsDirectory)
{
2020-03-28 04:33:26 +00:00
await Utils.DeleteDirectory(this);
2020-03-25 12:47:25 +00:00
}
}
2020-03-25 12:47:25 +00:00
2020-04-23 10:37:58 +00:00
public long Size => Exists ? new FileInfo(_path).Length : 0;
2020-03-23 12:57:18 +00:00
public DateTime LastModified
{
get => File.GetLastWriteTime(_path);
set => File.SetLastWriteTime(_path, value);
}
2020-03-23 12:57:18 +00:00
public DateTime LastModifiedUtc => File.GetLastWriteTimeUtc(_path);
public AbsolutePath Parent => (AbsolutePath)Path.GetDirectoryName(_path);
public RelativePath FileName => (RelativePath)Path.GetFileName(_path);
2020-03-25 12:47:25 +00:00
public RelativePath FileNameWithoutExtension => (RelativePath)Path.GetFileNameWithoutExtension(_path);
2020-03-27 03:33:24 +00:00
public bool IsEmptyDirectory => IsDirectory && !EnumerateFiles().Any();
2020-03-23 12:57:18 +00:00
public bool IsReadOnly
{
get
{
return new FileInfo(_path).IsReadOnly;
}
set
{
new FileInfo(_path).IsReadOnly = value;
}
}
2020-04-27 21:32:19 +00:00
public void SetReadOnly(bool val)
{
IsReadOnly = true;
}
/// <summary>
/// Returns the full path the folder that contains Wabbajack.Common. This will almost always be
/// where all the binaries for the project reside.
/// </summary>
/// <exception cref="ArgumentException"></exception>
2020-03-29 03:29:27 +00:00
public static AbsolutePath EntryPoint
{
get
{
var location = Assembly.GetExecutingAssembly().Location ?? null;
if (location == null)
throw new ArgumentException("Could not find entry point.");
2020-03-29 03:29:27 +00:00
return ((AbsolutePath)location).Parent;
}
}
public AbsolutePath Root => (AbsolutePath)Path.GetPathRoot(_path);
2020-03-28 18:22:53 +00:00
/// <summary>
/// Moves this file to the specified location, will use Copy if required
/// </summary>
/// <param name="otherPath"></param>
/// <param name="overwrite">Replace the destination file if it exists</param>
public async Task MoveToAsync(AbsolutePath otherPath, bool overwrite = false)
{
if (Root != otherPath.Root)
{
if (otherPath.Exists && overwrite)
2020-05-26 11:31:11 +00:00
await otherPath.DeleteAsync();
await CopyToAsync(otherPath);
2020-05-26 11:31:11 +00:00
await DeleteAsync();
return;
}
2020-03-23 12:57:18 +00:00
File.Move(_path, otherPath._path, overwrite ? MoveOptions.ReplaceExisting : MoveOptions.None);
}
public RelativePath RelativeTo(AbsolutePath p)
{
2020-05-04 12:11:53 +00:00
var relPath = Path.GetRelativePath(p._path, _path);
if (relPath == _path)
throw new ArgumentException($"{_path} is not a subpath of {p._path}");
return new RelativePath(relPath);
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public async Task<string> ReadAllTextAsync()
{
await using var fs = File.OpenRead(_path);
return Encoding.UTF8.GetString(await fs.ReadAllAsync());
}
/// <summary>
2020-03-25 12:47:25 +00:00
/// Assuming the path is a folder, enumerate all the files in the folder
2020-03-23 12:57:18 +00:00
/// </summary>
/// <param name="recursive">if true, also returns files in sub-folders</param>
/// <param name="pattern">pattern to match against</param>
2020-03-23 12:57:18 +00:00
/// <returns></returns>
public IEnumerable<AbsolutePath> EnumerateFiles(bool recursive = true, string pattern = "*")
2020-03-23 12:57:18 +00:00
{
2020-03-28 18:22:53 +00:00
if (!IsDirectory) return new AbsolutePath[0];
2020-03-23 12:57:18 +00:00
return Directory
.EnumerateFiles(_path, pattern, recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
2020-03-23 12:57:18 +00:00
.Select(path => new AbsolutePath(path, true));
}
#region Operators
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator string(AbsolutePath path)
{
return path._path;
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator AbsolutePath(string path)
{
2020-03-28 21:09:45 +00:00
if (string.IsNullOrEmpty(path)) return default;
2020-03-23 12:57:18 +00:00
return !Path.IsPathRooted(path) ? ((RelativePath)path).RelativeToEntryPoint() : new AbsolutePath(path);
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator ==(AbsolutePath a, AbsolutePath b)
{
return a.Equals(b);
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator !=(AbsolutePath a, AbsolutePath b)
{
return !a.Equals(b);
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
#endregion
public void CreateDirectory()
{
Directory.CreateDirectory(_path);
}
2020-05-26 11:31:11 +00:00
public async Task DeleteAsync()
{
2020-04-27 21:32:19 +00:00
if (!IsFile) return;
if (IsReadOnly) IsReadOnly = false;
2020-05-26 11:31:11 +00:00
var path = _path;
2020-06-01 20:26:03 +00:00
await CircuitBreaker.WithAutoRetryAsync<IOException>(async () => File.Delete(path));
}
public void Delete()
{
if (!IsFile) return;
if (IsReadOnly) IsReadOnly = false;
var path = _path;
CircuitBreaker.WithAutoRetry<IOException>(async () => File.Delete(path));
2020-03-25 12:47:25 +00:00
}
2020-03-25 22:30:43 +00:00
public bool InFolder(AbsolutePath folder)
2020-03-25 12:47:25 +00:00
{
2020-03-25 22:30:43 +00:00
return _path.StartsWith(folder._path + Path.DirectorySeparator);
2020-03-25 12:47:25 +00:00
}
public async Task<byte[]> ReadAllBytesAsync()
{
await using var f = await OpenShared();
2020-03-25 12:47:25 +00:00
return await f.ReadAllAsync();
}
public AbsolutePath WithExtension(Extension hashFileExtension)
{
2020-03-26 23:02:15 +00:00
return new AbsolutePath(_path + (string)hashFileExtension, true);
2020-03-25 12:47:25 +00:00
}
public AbsolutePath ReplaceExtension(Extension extension)
{
return new AbsolutePath(
Path.Combine(Path.GetDirectoryName(_path), Path.GetFileNameWithoutExtension(_path) + (string)extension),
true);
}
2020-04-01 19:11:34 +00:00
public AbsolutePath AppendToName(string toAppend)
2020-03-25 12:47:25 +00:00
{
return new AbsolutePath(
Path.Combine(Path.GetDirectoryName(_path),
Path.GetFileNameWithoutExtension(_path) + toAppend + (string)Extension));
}
public AbsolutePath Combine(params RelativePath[] paths)
{
return new AbsolutePath(Path.Combine(paths.Select(s => (string)s).Cons(_path).ToArray()));
2020-03-25 12:47:25 +00:00
}
public AbsolutePath Combine(params string[] paths)
{
2020-03-28 18:22:53 +00:00
2020-03-25 12:47:25 +00:00
return new AbsolutePath(Path.Combine(paths.Cons(_path).ToArray()));
}
public IEnumerable<string> ReadAllLines()
{
return File.ReadAllLines(_path);
}
public async Task WriteAllBytesAsync(byte[] data)
{
await using var fs = await Create();
2020-03-25 12:47:25 +00:00
await fs.WriteAsync(data);
}
2020-05-20 03:25:41 +00:00
public async Task WriteAllAsync(Stream data, bool disposeAfter = true)
{
await using var fs = await Create();
2020-05-20 03:55:12 +00:00
await data.CopyToAsync(fs);
2020-05-20 03:25:41 +00:00
if (disposeAfter) await data.DisposeAsync();
}
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
private static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes);
public bool HardLinkTo(AbsolutePath destination)
{
Utils.Log($"Hard Linking {_path} to {destination}");
return CreateHardLink((string)destination, (string)this, IntPtr.Zero);
}
public async ValueTask HardLinkIfOversize(AbsolutePath destination)
{
if (!destination.Parent.Exists)
destination.Parent.CreateDirectory();
if (Root == destination.Root && Consts.SupportedBSAs.Contains(Extension))
{
if (HardLinkTo(destination))
return;
}
await CopyToAsync(destination);
}
2020-03-25 12:47:25 +00:00
public async Task<IEnumerable<string>> ReadAllLinesAsync()
{
return (await ReadAllTextAsync()).Split(new[] {'\n', '\r'}, StringSplitOptions.RemoveEmptyEntries);
}
2020-03-25 22:30:43 +00:00
public static AbsolutePath GetCurrentDirectory()
{
return new AbsolutePath(Directory.GetCurrentDirectory());
}
public async Task CopyToAsync(AbsolutePath destFile)
{
await using var src = await OpenRead();
await using var dest = await destFile.Create();
2020-03-25 22:30:43 +00:00
await src.CopyToAsync(dest);
}
2020-03-25 23:15:19 +00:00
public IEnumerable<AbsolutePath> EnumerateDirectories(bool recursive = true)
{
return Directory.EnumerateDirectories(_path, "*", recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
.Select(p => (AbsolutePath)p);
}
public async Task WriteAllLinesAsync(params string[] strings)
{
2020-03-28 02:54:14 +00:00
await WriteAllTextAsync(string.Join("\r\n",strings));
2020-03-25 23:15:19 +00:00
}
2020-03-26 21:15:44 +00:00
2020-03-27 03:10:23 +00:00
public int CompareTo(AbsolutePath other)
{
return string.Compare(_path, other._path, StringComparison.Ordinal);
}
2020-03-28 20:04:22 +00:00
public string ReadAllText()
{
return File.ReadAllText(_path);
}
public ValueTask<FileStream> OpenShared()
2020-03-28 20:04:22 +00:00
{
var path = _path;
2020-06-01 20:26:03 +00:00
return CircuitBreaker.WithAutoRetryAsync<FileStream, IOException>(async () =>
File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
2020-03-28 20:04:22 +00:00
}
2020-03-30 20:38:46 +00:00
public ValueTask<FileStream> WriteShared()
2020-03-30 20:38:46 +00:00
{
var path = _path;
2020-06-01 20:26:03 +00:00
return CircuitBreaker.WithAutoRetryAsync<FileStream, IOException>(async () =>
File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite));
2020-03-30 20:38:46 +00:00
}
2020-03-31 22:05:36 +00:00
public async Task CopyDirectoryToAsync(AbsolutePath destination)
{
destination.CreateDirectory();
foreach (var file in EnumerateFiles())
{
var dest = file.RelativeTo(this).RelativeTo(destination);
await file.CopyToAsync(dest);
}
}
2020-03-23 12:57:18 +00:00
}
[JsonConverter(typeof(Utils.RelativePathConverter))]
2020-03-26 23:02:15 +00:00
public struct RelativePath : IPath, IEquatable<RelativePath>, IComparable<RelativePath>
2020-03-23 12:57:18 +00:00
{
private readonly string? _nullable_path;
private string _path => _nullable_path ?? string.Empty;
2020-03-23 12:57:18 +00:00
public RelativePath(string path)
{
2020-03-29 03:29:27 +00:00
if (string.IsNullOrWhiteSpace(path))
2020-03-28 18:22:53 +00:00
{
_nullable_path = null;
2020-03-29 03:29:27 +00:00
return;
2020-03-28 18:22:53 +00:00
}
var trimmed = path.Replace("/", "\\").Trim('\\');
2020-03-29 03:29:27 +00:00
if (string.IsNullOrEmpty(trimmed))
2020-03-28 18:22:53 +00:00
{
_nullable_path = null;
2020-03-29 03:29:27 +00:00
return;
2020-03-28 18:22:53 +00:00
}
2020-03-29 03:29:27 +00:00
_nullable_path = trimmed;
2020-03-23 12:57:18 +00:00
Validate();
}
2020-03-25 12:47:25 +00:00
public override string ToString()
{
return _path;
}
public Extension Extension => Extension.FromPath(_path);
2020-03-25 12:47:25 +00:00
2020-03-24 21:42:28 +00:00
public override int GetHashCode()
{
return _path.GetHashCode(StringComparison.InvariantCultureIgnoreCase);
2020-03-24 21:42:28 +00:00
}
2020-03-23 12:57:18 +00:00
public static RelativePath RandomFileName()
{
return (RelativePath)Guid.NewGuid().ToString();
}
private void Validate()
{
if (Path.IsPathRooted(_path))
2020-03-25 12:47:25 +00:00
{
2020-05-14 22:21:56 +00:00
throw new InvalidDataException($"Cannot create relative path from absolute path string, got {_path}");
2020-03-25 12:47:25 +00:00
}
2020-03-23 12:57:18 +00:00
}
public AbsolutePath RelativeTo(AbsolutePath abs)
{
return new AbsolutePath(Path.Combine((string)abs, _path));
2020-03-23 12:57:18 +00:00
}
public AbsolutePath RelativeToEntryPoint()
{
2020-03-29 03:29:27 +00:00
return RelativeTo(AbsolutePath.EntryPoint);
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public AbsolutePath RelativeToWorkingDirectory()
{
return RelativeTo((AbsolutePath)Directory.GetCurrentDirectory());
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator string(RelativePath path)
{
return path._path;
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator RelativePath(string path)
{
return new RelativePath(path);
}
public AbsolutePath RelativeToSystemDirectory()
{
return RelativeTo((AbsolutePath)Environment.SystemDirectory);
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public RelativePath Parent => (RelativePath)Path.GetDirectoryName(_path);
2020-03-25 12:47:25 +00:00
2020-03-24 12:21:19 +00:00
public RelativePath FileName => new RelativePath(Path.GetFileName(_path));
2020-03-24 21:42:28 +00:00
2020-03-25 22:30:43 +00:00
public RelativePath FileNameWithoutExtension => (RelativePath)Path.GetFileNameWithoutExtension(_path);
2020-03-28 18:22:53 +00:00
public RelativePath TopParent
{
get
{
var curr = this;
while (curr.Parent != default)
curr = curr.Parent;
return curr;
}
}
2020-03-24 21:42:28 +00:00
public bool Equals(RelativePath other)
{
return string.Equals(_path, other._path, StringComparison.InvariantCultureIgnoreCase);
2020-03-24 21:42:28 +00:00
}
public override bool Equals(object? obj)
2020-03-24 21:42:28 +00:00
{
return obj is RelativePath other && Equals(other);
}
2020-03-25 12:47:25 +00:00
2020-03-24 21:42:28 +00:00
public static bool operator ==(RelativePath a, RelativePath b)
{
return a.Equals(b);
2020-03-24 21:42:28 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-24 21:42:28 +00:00
public static bool operator !=(RelativePath a, RelativePath b)
{
return !a.Equals(b);
2020-03-24 21:42:28 +00:00
}
2020-03-25 12:47:25 +00:00
public bool StartsWith(string s)
{
return _path.StartsWith(s);
}
2020-03-25 22:30:43 +00:00
public bool StartsWith(RelativePath s)
{
return _path.StartsWith(s._path);
}
public RelativePath Combine(params RelativePath[] paths )
{
return (RelativePath)Path.Combine(paths.Select(p => (string)p).Cons(_path).ToArray());
2020-03-28 20:42:45 +00:00
}
public RelativePath Combine(params string[] paths)
2020-03-28 20:42:45 +00:00
{
return (RelativePath)Path.Combine(paths.Cons(_path).ToArray());
2020-03-25 22:30:43 +00:00
}
2020-03-26 23:02:15 +00:00
public int CompareTo(RelativePath other)
{
return string.Compare(_path, other._path, StringComparison.Ordinal);
}
2020-03-23 12:57:18 +00:00
}
public static partial class Utils
{
public static RelativePath ToPath(this string str)
{
return (RelativePath)str;
}
2020-03-25 12:47:25 +00:00
public static AbsolutePath RelativeTo(this string str, AbsolutePath path)
{
2020-05-29 04:09:39 +00:00
if (Path.IsPathRooted(str)) return (AbsolutePath)str;
return ((RelativePath)str).RelativeTo(path);
}
2020-03-24 12:21:19 +00:00
public static void Write(this BinaryWriter wtr, IPath path)
{
wtr.Write(path is AbsolutePath);
if (path is AbsolutePath)
2020-03-25 12:47:25 +00:00
{
2020-03-24 12:21:19 +00:00
wtr.Write((AbsolutePath)path);
2020-03-25 12:47:25 +00:00
}
2020-03-24 12:21:19 +00:00
else
2020-03-25 12:47:25 +00:00
{
2020-03-24 12:21:19 +00:00
wtr.Write((RelativePath)path);
2020-03-25 12:47:25 +00:00
}
2020-03-24 12:21:19 +00:00
}
public static void Write(this BinaryWriter wtr, AbsolutePath path)
{
wtr.Write((string)path);
}
2020-03-25 12:47:25 +00:00
2020-03-24 12:21:19 +00:00
public static void Write(this BinaryWriter wtr, RelativePath path)
{
wtr.Write((string)path);
}
public static IPath ReadIPath(this BinaryReader rdr)
{
if (rdr.ReadBoolean())
2020-03-25 12:47:25 +00:00
{
2020-03-24 12:21:19 +00:00
return rdr.ReadAbsolutePath();
2020-03-25 12:47:25 +00:00
}
2020-03-24 12:21:19 +00:00
return rdr.ReadRelativePath();
}
public static AbsolutePath ReadAbsolutePath(this BinaryReader rdr)
{
return new AbsolutePath(rdr.ReadString());
}
public static RelativePath ReadRelativePath(this BinaryReader rdr)
{
return new RelativePath(rdr.ReadString());
}
public static T[] Add<T>(this T[] arr, T itm)
{
var newArr = new T[arr.Length + 1];
Array.Copy(arr, 0, newArr, 0, arr.Length);
newArr[arr.Length] = itm;
return newArr;
}
}
2020-03-24 12:21:19 +00:00
public struct Extension
2020-03-23 12:57:18 +00:00
{
2020-03-25 12:47:25 +00:00
public static Extension None = new Extension("", false);
2020-03-23 12:57:18 +00:00
#region ObjectEquality
2020-03-25 12:47:25 +00:00
private bool Equals(Extension other)
2020-03-23 12:57:18 +00:00
{
return string.Equals(_extension, other._extension, StringComparison.InvariantCultureIgnoreCase);
2020-03-23 12:57:18 +00:00
}
public override bool Equals(object? obj)
2020-03-23 12:57:18 +00:00
{
return obj is Extension other && Equals(other);
2020-03-23 12:57:18 +00:00
}
2020-03-28 02:54:14 +00:00
public override string ToString()
{
return _extension;
}
2020-03-23 12:57:18 +00:00
public override int GetHashCode()
{
return _extension?.GetHashCode(StringComparison.InvariantCultureIgnoreCase) ?? 0;
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
#endregion
private readonly string? _nullable_extension;
private string _extension => _nullable_extension ?? string.Empty;
2020-03-23 12:57:18 +00:00
public Extension(string extension)
{
2020-03-24 12:21:19 +00:00
if (string.IsNullOrWhiteSpace(extension))
{
_nullable_extension = None._extension;
2020-03-24 12:21:19 +00:00
return;
}
_nullable_extension = string.Intern(extension);
2020-03-23 12:57:18 +00:00
Validate();
}
2020-03-24 12:21:19 +00:00
private Extension(string extension, bool validate)
{
_nullable_extension = string.Intern(extension);
2020-03-25 12:47:25 +00:00
if (validate)
{
Validate();
}
2020-03-24 12:21:19 +00:00
}
public Extension(Extension other)
{
_nullable_extension = other._extension;
2020-03-24 12:21:19 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
private void Validate()
{
if (!_extension.StartsWith("."))
2020-03-25 12:47:25 +00:00
{
2020-03-28 21:52:43 +00:00
throw new InvalidDataException($"Extensions must start with '.' got {_extension}");
2020-03-25 12:47:25 +00:00
}
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator string(Extension path)
{
return path._extension;
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static explicit operator Extension(string path)
{
return new Extension(path);
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator ==(Extension a, Extension b)
{
// Super fast comparison because extensions are interned
return ReferenceEquals(a._extension, b._extension);
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator !=(Extension a, Extension b)
{
2020-03-24 02:58:39 +00:00
return !(a == b);
2020-03-23 12:57:18 +00:00
}
2020-03-24 12:21:19 +00:00
public static Extension FromPath(string path)
{
var ext = Path.GetExtension(path);
return !string.IsNullOrWhiteSpace(ext) ? new Extension(ext) : None;
}
2020-03-23 12:57:18 +00:00
}
2020-03-26 12:28:03 +00:00
public struct HashRelativePath : IEquatable<HashRelativePath>
2020-03-23 12:57:18 +00:00
{
2020-03-24 12:21:19 +00:00
private static RelativePath[] EMPTY_PATH;
public Hash BaseHash { get; }
public RelativePath[] Paths { get; }
static HashRelativePath()
{
EMPTY_PATH = new RelativePath[0];
}
2020-03-25 12:47:25 +00:00
2020-03-24 12:21:19 +00:00
public HashRelativePath(Hash baseHash, params RelativePath[] paths)
{
BaseHash = baseHash;
Paths = paths;
}
2020-03-23 12:57:18 +00:00
2020-03-26 12:28:03 +00:00
public override string ToString()
2020-03-23 12:57:18 +00:00
{
2020-04-04 04:02:53 +00:00
var paths = Paths == null ? EmptyPath : Paths;
return string.Join("|", paths.Select(t => t.ToString()).Cons(BaseHash.ToString()));
2020-03-23 12:57:18 +00:00
}
2020-04-04 04:02:53 +00:00
private static RelativePath[] EmptyPath = Array.Empty<RelativePath>();
2020-03-23 12:57:18 +00:00
public static bool operator ==(HashRelativePath a, HashRelativePath b)
{
2020-04-04 04:02:53 +00:00
if (a.Paths == null || b.Paths == null) return false;
2020-03-26 12:28:03 +00:00
if (a.BaseHash != b.BaseHash || a.Paths.Length != b.Paths.Length)
2020-03-25 12:47:25 +00:00
{
2020-03-23 12:57:18 +00:00
return false;
2020-03-25 12:47:25 +00:00
}
for (var idx = 0; idx < a.Paths.Length; idx += 1)
{
2020-03-23 12:57:18 +00:00
if (a.Paths[idx] != b.Paths[idx])
2020-03-25 12:47:25 +00:00
{
2020-03-23 12:57:18 +00:00
return false;
2020-03-25 12:47:25 +00:00
}
}
2020-03-23 12:57:18 +00:00
return true;
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator !=(HashRelativePath a, HashRelativePath b)
{
return !(a == b);
}
2020-03-26 12:28:03 +00:00
public bool Equals(HashRelativePath other)
{
return this == other;
}
public override bool Equals(object? obj)
2020-03-26 12:28:03 +00:00
{
return obj is HashRelativePath other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(BaseHash, Paths);
}
2020-03-26 21:31:25 +00:00
public static HashRelativePath FromStrings(string hash, params string[] paths)
{
return new HashRelativePath(Hash.FromBase64(hash), paths.Select(p => (RelativePath)p).ToArray());
}
2020-03-23 12:57:18 +00:00
}
2020-03-25 12:47:25 +00:00
2020-03-25 22:30:43 +00:00
public struct FullPath : IEquatable<FullPath>, IPath
2020-03-23 12:57:18 +00:00
{
public AbsolutePath Base { get; }
2020-04-04 04:02:53 +00:00
2020-03-23 12:57:18 +00:00
public RelativePath[] Paths { get; }
2020-03-24 21:42:28 +00:00
private readonly int _hash;
2020-03-26 12:28:03 +00:00
public FullPath(AbsolutePath basePath, params RelativePath[] paths)
2020-03-23 12:57:18 +00:00
{
Base = basePath;
2020-04-04 04:02:53 +00:00
Paths = paths == null ? Array.Empty<RelativePath>() : paths;
2020-03-24 21:42:28 +00:00
_hash = Base.GetHashCode();
foreach (var itm in Paths)
2020-03-25 12:47:25 +00:00
{
2020-03-24 21:42:28 +00:00
_hash ^= itm.GetHashCode();
2020-03-25 12:47:25 +00:00
}
2020-03-23 12:57:18 +00:00
}
2020-03-24 21:42:28 +00:00
public override string ToString()
2020-03-23 12:57:18 +00:00
{
2020-04-04 04:02:53 +00:00
var paths = Paths == null ? EmptyPath : Paths;
return string.Join("|", paths.Select(t => (string)t).Cons((string)Base));
2020-03-23 12:57:18 +00:00
}
2020-03-24 21:42:28 +00:00
public override int GetHashCode()
{
return _hash;
}
2020-04-04 04:02:53 +00:00
private static RelativePath[] EmptyPath = Array.Empty<RelativePath>();
2020-03-23 12:57:18 +00:00
public static bool operator ==(FullPath a, FullPath b)
{
2020-04-04 04:02:53 +00:00
if (a.Paths == null || b.Paths == null) return false;
2020-03-24 21:42:28 +00:00
if (a.Base != b.Base || a.Paths.Length != b.Paths.Length)
2020-03-25 12:47:25 +00:00
{
2020-03-23 12:57:18 +00:00
return false;
2020-03-25 12:47:25 +00:00
}
for (var idx = 0; idx < a.Paths.Length; idx += 1)
{
2020-03-23 12:57:18 +00:00
if (a.Paths[idx] != b.Paths[idx])
2020-03-25 12:47:25 +00:00
{
2020-03-23 12:57:18 +00:00
return false;
2020-03-25 12:47:25 +00:00
}
}
2020-03-23 12:57:18 +00:00
return true;
}
2020-03-25 12:47:25 +00:00
2020-03-23 12:57:18 +00:00
public static bool operator !=(FullPath a, FullPath b)
{
return !(a == b);
}
2020-03-24 21:42:28 +00:00
public bool Equals(FullPath other)
{
return this == other;
}
public override bool Equals(object? obj)
2020-03-24 21:42:28 +00:00
{
return obj is FullPath other && Equals(other);
}
2020-03-25 22:30:43 +00:00
public RelativePath FileName => Paths.Length == 0 ? Base.FileName : Paths.Last().FileName;
2020-03-23 12:57:18 +00:00
}
}