2021-09-27 12:42:46 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2022-05-31 23:14:27 +00:00
|
|
|
|
using System.Security.AccessControl;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
namespace Wabbajack.Paths.IO;
|
|
|
|
|
|
|
|
|
|
public static class AbsolutePathExtensions
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public const int BufferSize = 1024 * 128;
|
|
|
|
|
|
|
|
|
|
public static Stream Open(this AbsolutePath file, FileMode mode, FileAccess access = FileAccess.Read,
|
|
|
|
|
FileShare share = FileShare.ReadWrite)
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return File.Open(file.ToNativePath(), mode, access, share);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static void Delete(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
var path = file.ToNativePath();
|
|
|
|
|
if (File.Exists(path))
|
2022-05-31 23:14:27 +00:00
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
}
|
2022-10-07 21:02:16 +00:00
|
|
|
|
catch (UnauthorizedAccessException)
|
2022-05-31 23:14:27 +00:00
|
|
|
|
{
|
|
|
|
|
var fi = new FileInfo(path);
|
|
|
|
|
if (fi.IsReadOnly)
|
|
|
|
|
{
|
|
|
|
|
fi.IsReadOnly = false;
|
|
|
|
|
File.Delete(path);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
throw;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
if (Directory.Exists(path))
|
2022-05-31 23:14:27 +00:00
|
|
|
|
file.DeleteDirectory();
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static long Size(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return new FileInfo(file.ToNativePath()).Length;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static DateTime LastModifiedUtc(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return new FileInfo(file.ToNativePath()).LastWriteTimeUtc;
|
|
|
|
|
}
|
2022-10-30 13:16:12 +00:00
|
|
|
|
|
|
|
|
|
public static DateTime CreatedUtc(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return new FileInfo(file.ToNativePath()).CreationTimeUtc;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static DateTime LastModified(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return new FileInfo(file.ToNativePath()).LastWriteTime;
|
|
|
|
|
}
|
2022-06-08 03:48:13 +00:00
|
|
|
|
|
|
|
|
|
public static void Touch(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
new FileInfo(file.ToNativePath()).LastWriteTime = DateTime.Now;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static byte[] ReadAllBytes(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
using var s = File.Open(file.ToNativePath(), FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
|
|
|
var remain = s.Length;
|
|
|
|
|
var length = remain;
|
|
|
|
|
var bytes = new byte[length];
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
while (remain > 0) remain -= s.Read(bytes, (int) Math.Min(length - remain, 1024 * 1024), bytes.Length);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return bytes;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static string ReadAllText(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return Encoding.UTF8.GetString(file.ReadAllBytes());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async IAsyncEnumerable<string> ReadAllLinesAsync(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
await using var fs = file.Open(FileMode.Open);
|
|
|
|
|
var sr = new StreamReader(fs);
|
|
|
|
|
while (true)
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var line = await sr.ReadLineAsync();
|
|
|
|
|
if (line == null) break;
|
|
|
|
|
yield return line;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static IEnumerable<string> ReadAllLines(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
using var fs = file.Open(FileMode.Open);
|
|
|
|
|
var sr = new StreamReader(fs);
|
|
|
|
|
while (true)
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var line = sr.ReadLine();
|
|
|
|
|
if (line == null) break;
|
|
|
|
|
yield return line;
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async Task<string> ReadAllTextAsync(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return Encoding.UTF8.GetString(await file.ReadAllBytesAsync());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async ValueTask<byte[]> ReadAllBytesAsync(this AbsolutePath file,
|
|
|
|
|
CancellationToken token = default)
|
|
|
|
|
{
|
|
|
|
|
await using var s = File.Open(file.ToNativePath(), FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
|
|
|
var remain = s.Length;
|
|
|
|
|
var length = remain;
|
|
|
|
|
var bytes = new byte[length];
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
while (remain > 0)
|
|
|
|
|
remain -= await s.ReadAsync(bytes.AsMemory((int) Math.Min(length - remain, 1024 * 1024), bytes.Length),
|
|
|
|
|
token);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return bytes;
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static void WriteAllBytes(this AbsolutePath file, ReadOnlySpan<byte> data)
|
|
|
|
|
{
|
|
|
|
|
using var s = file.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
|
|
|
|
s.Write(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static async Task WriteAllAsync(this AbsolutePath file, Stream srcStream, CancellationToken token,
|
|
|
|
|
bool closeWhenDone = true)
|
|
|
|
|
{
|
|
|
|
|
var buff = new byte[BufferSize];
|
|
|
|
|
await using var dest = file.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
|
|
|
|
while (true)
|
2021-09-27 12:42:46 +00:00
|
|
|
|
{
|
2021-10-23 16:51:17 +00:00
|
|
|
|
var read = await srcStream.ReadAsync(buff.AsMemory(0, BufferSize), token);
|
|
|
|
|
if (read == 0)
|
|
|
|
|
break;
|
|
|
|
|
await dest.WriteAsync(buff.AsMemory(0, read), token);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
if (closeWhenDone)
|
|
|
|
|
await srcStream.DisposeAsync();
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async Task WriteAllLinesAsync(this AbsolutePath file, IEnumerable<string> src,
|
|
|
|
|
CancellationToken token, bool closeWhenDone = true)
|
|
|
|
|
{
|
|
|
|
|
await using var dest = file.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
|
|
|
|
await using var sw = new StreamWriter(dest, Encoding.UTF8);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
foreach (var line in src) await sw.WriteLineAsync(line);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
await sw.DisposeAsync();
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async ValueTask WriteAllBytesAsync(this AbsolutePath file, Memory<byte> data,
|
|
|
|
|
CancellationToken token = default)
|
|
|
|
|
{
|
|
|
|
|
await using var s = file.Open(FileMode.Create, FileAccess.Write, FileShare.None);
|
|
|
|
|
await s.WriteAsync(data, token);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async ValueTask MoveToAsync(this AbsolutePath src, AbsolutePath dest, bool overwrite,
|
|
|
|
|
CancellationToken token)
|
|
|
|
|
{
|
|
|
|
|
// TODO: Make this async
|
2022-06-29 13:18:04 +00:00
|
|
|
|
var srcStr = src.ToString();
|
|
|
|
|
var destStr = dest.ToString();
|
|
|
|
|
var fi = new FileInfo(srcStr);
|
|
|
|
|
if (fi.IsReadOnly)
|
|
|
|
|
fi.IsReadOnly = false;
|
|
|
|
|
|
|
|
|
|
var fid = new FileInfo(destStr);
|
|
|
|
|
if (dest.FileExists() && fid.IsReadOnly)
|
|
|
|
|
{
|
|
|
|
|
fid.IsReadOnly = false;
|
|
|
|
|
}
|
2022-10-04 04:43:21 +00:00
|
|
|
|
|
|
|
|
|
var retries = 0;
|
|
|
|
|
while (true)
|
2022-06-29 13:18:04 +00:00
|
|
|
|
{
|
2022-10-04 04:43:21 +00:00
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
File.Move(srcStr, destStr, overwrite);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2022-10-07 21:02:16 +00:00
|
|
|
|
catch (Exception)
|
2022-10-04 04:43:21 +00:00
|
|
|
|
{
|
|
|
|
|
if (retries > 10)
|
|
|
|
|
throw;
|
|
|
|
|
retries++;
|
|
|
|
|
await Task.Delay(TimeSpan.FromSeconds(1), token);
|
|
|
|
|
}
|
2022-06-29 13:18:04 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2022-10-07 21:02:16 +00:00
|
|
|
|
public static async ValueTask CopyToAsync(this AbsolutePath src, AbsolutePath dest,
|
2021-10-23 16:51:17 +00:00
|
|
|
|
CancellationToken token)
|
|
|
|
|
{
|
2022-10-07 21:02:16 +00:00
|
|
|
|
await using var inf = src.Open(FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
|
|
|
await using var ouf = dest.Open(FileMode.Create, FileAccess.Write, FileShare.Read);
|
|
|
|
|
await inf.CopyToAsync(ouf, token);
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static void WriteAllText(this AbsolutePath file, string str)
|
|
|
|
|
{
|
|
|
|
|
file.WriteAllBytes(Encoding.UTF8.GetBytes(str));
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static async Task WriteAllTextAsync(this AbsolutePath file, string str,
|
|
|
|
|
CancellationToken token = default)
|
|
|
|
|
{
|
|
|
|
|
await file.WriteAllBytesAsync(Encoding.UTF8.GetBytes(str), token);
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
private static string ToNativePath(this AbsolutePath file)
|
|
|
|
|
{
|
|
|
|
|
return file.ToString();
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
#region Directories
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static void CreateDirectory(this AbsolutePath path)
|
|
|
|
|
{
|
2021-10-23 23:18:43 +00:00
|
|
|
|
if (path.Depth > 1 && !path.Parent.DirectoryExists())
|
|
|
|
|
path.Parent.CreateDirectory();
|
2021-10-23 16:51:17 +00:00
|
|
|
|
Directory.CreateDirectory(ToNativePath(path));
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static void DeleteDirectory(this AbsolutePath path, bool dontDeleteIfNotEmpty = false)
|
|
|
|
|
{
|
|
|
|
|
if (!path.DirectoryExists()) return;
|
|
|
|
|
if (dontDeleteIfNotEmpty && (path.EnumerateFiles().Any() || path.EnumerateDirectories().Any())) return;
|
2021-12-19 21:52:06 +00:00
|
|
|
|
|
2022-05-31 23:14:27 +00:00
|
|
|
|
foreach (var directory in Directory.GetDirectories(path.ToString()))
|
2021-12-19 21:52:06 +00:00
|
|
|
|
{
|
|
|
|
|
DeleteDirectory(directory.ToAbsolutePath(), dontDeleteIfNotEmpty);
|
|
|
|
|
}
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-05-31 23:14:27 +00:00
|
|
|
|
var di = new DirectoryInfo(path.ToString());
|
|
|
|
|
if (di.Attributes.HasFlag(FileAttributes.ReadOnly))
|
|
|
|
|
di.Attributes &= ~FileAttributes.ReadOnly;
|
2022-06-29 13:18:04 +00:00
|
|
|
|
Directory.Delete(path.ToString(), true);
|
2021-12-19 21:52:06 +00:00
|
|
|
|
}
|
|
|
|
|
catch (UnauthorizedAccessException)
|
|
|
|
|
{
|
|
|
|
|
Directory.Delete(path.ToString(), true);
|
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static bool DirectoryExists(this AbsolutePath path)
|
|
|
|
|
{
|
|
|
|
|
return path != default && Directory.Exists(path.ToNativePath());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static bool FileExists(this AbsolutePath path)
|
|
|
|
|
{
|
|
|
|
|
if (path == default) return false;
|
|
|
|
|
return File.Exists(path.ToNativePath());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static IEnumerable<AbsolutePath> EnumerateFiles(this AbsolutePath path, string pattern = "*",
|
|
|
|
|
bool recursive = true)
|
|
|
|
|
{
|
|
|
|
|
return Directory.EnumerateFiles(path.ToString(), pattern,
|
|
|
|
|
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
|
|
|
|
.Select(file => file.ToAbsolutePath());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
public static IEnumerable<AbsolutePath> EnumerateFiles(this AbsolutePath path, Extension pattern,
|
|
|
|
|
bool recursive = true)
|
|
|
|
|
{
|
|
|
|
|
return Directory.EnumerateFiles(path.ToString(), "*" + pattern,
|
|
|
|
|
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
|
|
|
|
.Select(file => file.ToAbsolutePath());
|
|
|
|
|
}
|
2021-09-27 12:42:46 +00:00
|
|
|
|
|
2021-10-23 16:51:17 +00:00
|
|
|
|
|
|
|
|
|
public static IEnumerable<AbsolutePath> EnumerateDirectories(this AbsolutePath path, bool recursive = true)
|
|
|
|
|
{
|
2022-07-11 20:55:54 +00:00
|
|
|
|
if (!path.DirectoryExists()) return Array.Empty<AbsolutePath>();
|
2021-10-23 16:51:17 +00:00
|
|
|
|
return Directory.EnumerateDirectories(path.ToString(), "*",
|
|
|
|
|
recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly)
|
|
|
|
|
.Select(p => (AbsolutePath) p);
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|
2021-10-23 16:51:17 +00:00
|
|
|
|
|
|
|
|
|
#endregion
|
2021-09-27 12:42:46 +00:00
|
|
|
|
}
|