Replace LessIO code mirror with submodule

This commit is contained in:
Matt Nadareski
2021-04-02 15:08:05 -07:00
parent 8960ad3b16
commit f6b58223de
11 changed files with 61 additions and 1118 deletions

3
.gitmodules vendored Normal file
View File

@@ -0,0 +1,3 @@
[submodule "BurnOutSharp/External/LessIO"]
path = BurnOutSharp/External/LessIO
url = https://github.com/activescott/LessIO.git

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0-windows</TargetFrameworks>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<Title>BurnOutSharp</Title>
<AssemblyName>BurnOutSharp</AssemblyName>
@@ -28,6 +28,11 @@
<PackageReference Include="WiseUnpacker" Version="1.0.2" />
</ItemGroup>
<!-- These are needed for dealing with submodules -->
<PropertyGroup>
<DefaultItemExcludes>$(DefaultItemExcludes);**\AssemblyInfo.cs;External\LessIO\src\LessIO.Tests\**\*</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<None Include="LICENSE.txt" Pack="true" PackagePath="$(PackageLicenseFile)" />
</ItemGroup>

1
BurnOutSharp/External/LessIO vendored Submodule

View File

@@ -1,55 +0,0 @@
using System;
namespace LessIO
{
//TODO: These are the samea s Win32. Consider whether we should expose all of these?
/// <summary>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/gg258117%28v=vs.85%29.aspx
/// </summary>
/// <remarks>
/// These have the same values as <see cref="System.IO.FileAttributes"/> they can generally be casted between these enums (this Enum has more values than System.IO though).
/// Defined in C:\Program Files (x86)\Windows Kits\8.1\Include\um\winnt.h
/// </remarks>
[Flags]
public enum FileAttributes
{
//#define FILE_ATTRIBUTE_READONLY 0x00000001
ReadOnly = 0x00000001,
//#define FILE_ATTRIBUTE_HIDDEN 0x00000002
Hidden = 0x00000002,
//#define FILE_ATTRIBUTE_SYSTEM 0x00000004
System = 0x00000004,
//#define FILE_ATTRIBUTE_DIRECTORY 0x00000010
Directory = 0x00000010,
//#define FILE_ATTRIBUTE_ARCHIVE 0x00000020
Archive = 0x00000020,
//#define FILE_ATTRIBUTE_DEVICE 0x00000040
Device = 0x00000040,
//#define FILE_ATTRIBUTE_NORMAL 0x00000080
Normal = 0x00000080,
//#define FILE_ATTRIBUTE_TEMPORARY 0x00000100
Temporary = 0x00000100,
//#define FILE_ATTRIBUTE_SPARSE_FILE 0x00000200
SparseFile = 0x00000200,
//#define FILE_ATTRIBUTE_REPARSE_POINT 0x00000400
ReparsePoint = 0x00000400,
//#define FILE_ATTRIBUTE_COMPRESSED 0x00000800
Compressed = 0x00000800,
//#define FILE_ATTRIBUTE_OFFLINE 0x00001000
Offline = 0x00001000,
//#define FILE_ATTRIBUTE_NOT_CONTENT_INDEXED 0x00002000
NotContentIndexed = 0x00002000,
//#define FILE_ATTRIBUTE_ENCRYPTED 0x00004000
Encrypted = 0x00004000,
//#define FILE_ATTRIBUTE_INTEGRITY_STREAM 0x00008000
IntegrityStream = 0x00008000,
//#define FILE_ATTRIBUTE_VIRTUAL 0x00010000
Virtual = 0x00010000,
//#define FILE_ATTRIBUTE_NO_SCRUB_DATA 0x00020000
NoScrubData = 0x00020000,
/* EA is not documented
#define FILE_ATTRIBUTE_EA 0x00040000
EA = 0x00040000
*/
}
}

View File

@@ -1,160 +0,0 @@
using System;
using LessIO.Strategies;
using LessIO.Strategies.Win32;
using BadPath = System.IO.Path;
using System.Collections.Generic;
namespace LessIO
{
/// <summary>
/// Provides various file system operations for the current platform.
/// </summary>
public static class FileSystem
{
private static readonly Lazy<FileSystemStrategy> LazyStrategy = new Lazy<FileSystemStrategy>(() => new Win32FileSystemStrategy());
private static FileSystemStrategy Strategy
{
get { return LazyStrategy.Value; }
}
/// <summary>
/// Sets the date and time that the specified file was last written to.
/// </summary>
/// <param name="path">The path of the file to set the file time on.</param>
/// <param name="lastWriteTime">
/// The date to set for the last write date and time of specified file.
/// Expressed in local time.
/// </param>
public static void SetLastWriteTime(Path path, DateTime lastWriteTime)
{
Strategy.SetLastWriteTime(path, lastWriteTime);
}
public static void SetAttributes(Path path, LessIO.FileAttributes fileAttributes)
{
Strategy.SetAttributes(path, fileAttributes);
}
public static LessIO.FileAttributes GetAttributes(Path path)
{
return Strategy.GetAttributes(path);
}
/// <summary>
/// Returns true if a file or directory exists at the specified path.
/// </summary>
public static bool Exists(Path path)
{
return Strategy.Exists(path);
}
/// <summary>
/// Creates the specified directory.
/// </summary>
/// <remarks>
/// Creates parent directories as needed.
/// </remarks>
public static void CreateDirectory(Path path)
{
Strategy.CreateDirectory(path);
}
/// <summary>
/// Removes/deletes an existing empty directory.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365488%28v=vs.85%29.aspx
/// </summary>
/// <param name="path"></param>
public static void RemoveDirectory(Path path)
{
RemoveDirectory(path, false);
}
/// <summary>
/// Removes/deletes an existing directory.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365488%28v=vs.85%29.aspx
/// </summary>
/// <param name="path">The path to the directory to remove.</param>
/// <param name="recursively">
/// True to remove the directory and all of its contents recursively.
/// False will remove the directory only if it is empty.
/// </param>
/// <remarks>Recursively implies removing contained files forcefully.</remarks>
public static void RemoveDirectory(Path path, bool recursively)
{
Strategy.RemoveDirectory(path, recursively);
}
/// <summary>
/// Removes/deletes an existing file.
/// To remove a directory see <see cref="RemoveDirectory"/>.
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363915%28v=vs.85%29.aspx
/// </summary>
/// <param name="path">The file to remove.</param>
/// <param name="forcefully">True to remove the file even if it is read-only.</param>
public static void RemoveFile(Path path, bool forcefully)
{
Strategy.RemoveFile(path, forcefully);
}
/// <summary>
/// Removes/deletes an existing file.
/// To remove a directory see <see cref="RemoveDirectory"/>.
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363915%28v=vs.85%29.aspx
/// </summary>
/// <param name="path">The file to remove.</param>
public static void RemoveFile(Path path)
{
Strategy.RemoveFile(path, false);
}
/// <summary>
/// Copies the specified existing file to a new location.
/// Will throw an exception if the destination file already exists.
/// </summary>
public static void Copy(Path source, Path dest)
{
if (!Strategy.Exists(source))
throw new Exception(string.Format("The file \"{0}\" does not exist.", source));
Strategy.Copy(source, dest);
}
/// <summary>
/// Returns true if the specified path is a directory.
/// </summary>
internal static bool IsDirectory(Path path)
{
return Strategy.IsDirectory(path);
}
/// <summary>
/// Creates or overwrites the file at the specified path.
/// </summary>
/// <param name="path">The path and name of the file to create. Supports long file paths.</param>
/// <returns>A <see cref="System.IO.Stream"/> that provides read/write access to the file specified in path.</returns>
public static System.IO.Stream CreateFile(Path path)
{
return Strategy.CreateFile(path);
}
/// <summary>
/// Returns the contents of the specified directory.
/// </summary>
/// <param name="directory">The path to the directory to get the contents of.</param>
public static IEnumerable<Path> ListContents(Path directory)
{
return Strategy.ListContents(directory);
}
/// <summary>
/// Returns the contents of the specified directory.
/// </summary>
/// <param name="directory">The path to the directory to get the contents of.</param>
/// <param name="recursive">True to list the contents of any child directories.</param>
/// <remarks>If the specified directory is not actually a directory then an empty set is returned.</remarks>
public static IEnumerable<Path> ListContents(Path directory, bool recursive)
{
return Strategy.ListContents(directory, recursive);
}
}
}

View File

@@ -1,399 +0,0 @@
using System;
using System.Linq;
using BadPath = System.IO.Path;
namespace LessIO
{
/// <summary>
/// Represents a file system path.
/// </summary>
public struct Path : IEquatable<Path>
{
private readonly string _path;
private static readonly string _pathEmpty = string.Empty;
public static readonly Path Empty = new Path();
/// <summary>
/// This is the special prefix to prepend to paths to support up to 32,767 character paths.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
/// </summary>
private static readonly string Win32LongPathPrefix = @"\\?\";
/// <summary>
/// This is the special prefix to prepend to paths to support long paths for UNC paths.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
/// </summary>
private static readonly string Win32LongPathPrefixUNC = @"\\?\UNC\";
private static readonly string UNCPrefix = @"\\";
// TODO: Add validation using a strategy? Or just use it as a strongly typed path to force caller to be explicit?
public Path(string path)
{
//TODO: Consider doing a FileSystem.Normalize and FileSystem.Validate to allow the strategy to Normalize & Validate path
//To maintain sanity NEVER let the Path object store the long path prefixes. That is a hack for Win32 that should only ever be used just before calling the Win32 API and stripped out of any paths coming out of the Win32 API.
path = StripWin32PathPrefix(path);
path = StripDirectorySeperatorPostfix(path);
path = RemoveDoubleSeperators(path);
_path = path;
}
private static string RemoveDoubleSeperators(string path)
{
/*
"\\" is legit for UNC paths and as a prefix.
So don't remove "\\" if it is in the root.
*/
string root = GetPathRoot(path);
string remainder = path.Length > root.Length ? path.Substring(root.Length) : "";
Array.ForEach(DirectorySeperatorChars, sep => remainder = remainder.Replace(new string(new char[] { sep, sep }), new string(new char[] { sep })));
return root + remainder;
}
private static string StripDirectorySeperatorPostfix(string path)
{
/* Here we want to trim any trailing directory seperator charactars EXCEPT
in one case: When the path is a fully qualified root dir such as "x:\". See GetPathRoot and System.IO.Path.GetPathRoot
*/
// "X:/"(path specified an absolute path on a given drive).
if (path.Length == 3 && path[1] == ':' && IsDirectorySeparator(path[2]))
return path;
else
return path.TrimEnd(DirectorySeperatorChars);
}
private static string StripWin32PathPrefix(string pathString)
{
if (pathString.StartsWith(Win32LongPathPrefixUNC))
return UNCPrefix + pathString.Substring(Win32LongPathPrefixUNC.Length);
if (pathString.StartsWith(Win32LongPathPrefix))
return pathString.Substring(Win32LongPathPrefix.Length);
return pathString;
}
/// <summary>
/// Returns the directory seperator characers.
/// </summary>
internal static char[] DirectorySeperatorChars
{
get
{
return new char[] { BadPath.DirectorySeparatorChar, BadPath.AltDirectorySeparatorChar };
}
}
internal static bool IsDirectorySeparator(char ch)
{
return Array.Exists<char>(DirectorySeperatorChars, c => c == ch);
}
/// <summary>
/// Gets the normalized path string. May be rooted or may be relative.
/// For a rooted/qualified path use <see cref="FullPathString"/>
/// </summary>
public string PathString
{
get
{
return _path != null ? _path : _pathEmpty;
}
}
/// <summary>
/// Returns the absolute path for the current path.
/// Compatible with <see cref="System.IO.Path.GetFullPath(string)"/>.
/// </summary>
public Path FullPath
{
get
{
return new Path(this.FullPathString);
}
}
/// <summary>
/// Returns the absolute path for the current path.
/// Compatible with <see cref="System.IO.Path.GetFullPath(string)"/>.
/// </summary>
public string FullPathString
{
get
{
var pathString = this.PathString;
var pathRoot = this.PathRoot;
if (pathRoot == "")
{ // relative
return Combine(WorkingDirectory, pathString).PathString;
}
else if (pathRoot == @"\" || pathRoot == @"/")
{ // use the working directory's drive/root only.
pathString = pathString.TrimStart(DirectorySeperatorChars);//otherwise Combine will ignore the root
string workingRoot = new Path(WorkingDirectory).PathRoot;
return Combine(workingRoot, pathString).PathString;
}
else
{
return pathString;
}
}
}
private static string WorkingDirectory
{
get {
//TODO: There is a Win32 native equivelent for this:
return System.IO.Directory.GetCurrentDirectory();
}
}
public bool IsEmpty
{
get { return Equals(Path.Empty); }
}
/// <summary>
/// Indicates if the two paths are equivelent and point to the same file or directory.
/// </summary>
private static bool PathEquals(string pathA, string pathB)
{
/* Now we never let the Win32 long path prefix get into a Path instance:
pathA = StripWin32PathPrefix(pathA);
pathB = StripWin32PathPrefix(pathB);
*/
pathA = pathA.TrimEnd(DirectorySeperatorChars);
pathB = pathB.TrimEnd(DirectorySeperatorChars);
var partsA = pathA.Split(DirectorySeperatorChars);
var partsB = pathB.Split(DirectorySeperatorChars);
if (partsA.Length != partsB.Length)
return false;
for (var i = 0; i < partsA.Length; i++)
{
var areEqual = string.Equals(partsA[i], partsB[i], StringComparison.InvariantCultureIgnoreCase);
if (!areEqual)
return false;
}
return true;
}
public static bool operator ==(Path a, Path b)
{
return Path.Equals(a, b);
}
public static bool operator !=(Path a, Path b)
{
return !Path.Equals(a, b);
}
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
return Equals((Path)obj);
}
public bool Equals(Path other)
{
return Path.PathEquals(this.PathString, other.PathString);
}
internal static bool Equals(Path a, Path b)
{
return PathEquals(a.PathString, b.PathString);
}
/// <summary>
/// Long-form filenames are not supported by the .NET system libraries, so we do win32 calls.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247%28v=vs.85%29.aspx#maxpath
/// </summary>
/// <remarks>
/// The <see cref="Path"/> object will never store the Win32 long path prefix. Instead use this method to add it back when necessary (i.e. when making direct calls into Win32 APIs).
/// </remarks>
public string WithWin32LongPathPrefix()
{
if (!PathString.StartsWith(Win32LongPathPrefix)) // More consistent to deal with if we just add it to all of them: if (!path.StartsWith(LongPathPrefix) && path.Length >= MAX_PATH)
{
if (PathString.StartsWith(UNCPrefix))
return Win32LongPathPrefixUNC + this.PathString.Substring(UNCPrefix.Length);
else
return Win32LongPathPrefix + this.PathString;
}
else
{
//NOTE that Win32LongPathPrefixUNC is a superset of Win32LongPathPrefix we just assume the right pathprefix is already there.
return this.PathString;
}
}
public override int GetHashCode()
{
return PathString.GetHashCode();
}
public override string ToString()
{
return PathString.ToString();
}
/// <summary>
/// Gets the root directory information of the specified path.
/// </summary>
public string PathRoot
{
get
{
return GetPathRoot(this.PathString);
}
}
/// <summary>
/// Modeled after <see cref="System.IO.Path.GetPathRoot(string)"/> but supports long path names.
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
/// <remarks>
/// See https://msdn.microsoft.com/en-us/library/system.io.path.getpathroot%28v=vs.110%29.aspx
/// Possible patterns for the string returned by this method are as follows:
/// An empty string (path specified a relative path on the current drive or volume).
/// "/"(path specified an absolute path on the current drive).
/// "X:"(path specified a relative path on a drive, where X represents a drive or volume letter).
/// "X:/"(path specified an absolute path on a given drive).
/// "\\ComputerName\SharedFolder"(a UNC path).
/// </remarks>
internal static string GetPathRoot(string path)
{
// "X:/"(path specified an absolute path on a given drive).
if (path.Length >= 3 && path[1] == ':' && IsDirectorySeparator(path[2]))
return path.Substring(0, 3);
// "X:"(path specified a relative path on a drive, where X represents a drive or volume letter).
if (path.Length >= 2 && path[1] == ':')
{
return path.Substring(0, 2);
}
// "\\ComputerName\SharedFolder"(a UNC path).
// NOTE: UNC Path "root" includes the server/host AND have the root share folder too.
if (path.Length > 2
&& IsDirectorySeparator(path[0])
&& IsDirectorySeparator(path[1])
&& path.IndexOfAny(DirectorySeperatorChars, 2) > 2)
{
var beginShareName = path.IndexOfAny(DirectorySeperatorChars, 2);
var endShareName = path.IndexOfAny(DirectorySeperatorChars, beginShareName + 1);
if (endShareName < 0)
endShareName = path.Length;
if (beginShareName > 2 && endShareName > beginShareName)
return path.Substring(0, endShareName);
}
// "/"(path specified an absolute path on the current drive).
if (path.Length >= 1 && IsDirectorySeparator(path[0]))
{
return path.Substring(0, 1);
}
// path specified a relative path on the current drive or volume?
return "";
}
public static Path Combine(Path path1, params string[] pathParts)
{
if (path1.IsEmpty)
throw new ArgumentNullException("path1");
if (pathParts == null || pathParts.Length == 0)
throw new ArgumentNullException("pathParts");
string[] allStrings = new string[pathParts.Length + 1];
allStrings[0] = path1.PathString;
Array.Copy(pathParts, 0, allStrings, 1, pathParts.Length);
return Combine(allStrings);
}
public static Path Combine(params Path[] pathParts)
{
if (pathParts == null)
throw new ArgumentNullException();
var strs = pathParts.Select(p => p.PathString);
return Combine(strs.ToArray());
}
public static Path Combine(params string[] pathParts)
{
if (pathParts == null)
throw new ArgumentNullException();
if (pathParts.Length < 2)
throw new ArgumentException("Expected at least two parts to combine.");
var output = BadPath.Combine(pathParts[0], pathParts[1]);
for (var i = 2; i < pathParts.Length; i++)
{
output = BadPath.Combine(output, pathParts[i]);
}
return new Path(output);
}
public Path Parent
{
get
{
var path = this.PathString;
path = path.TrimEnd(Path.DirectorySeperatorChars);
var parentEnd = path.LastIndexOfAny(Path.DirectorySeperatorChars);
if (parentEnd >= 0 && parentEnd > GetPathRoot(path).Length)
{
var result = path.Substring(0, parentEnd);
return new Path(result);
}
else
return Path.Empty;
}
}
/// <summary>
/// Indicates if the file or directory at the specified path exists.
/// For code compatibility with <see cref="System.IO.FileSystemInfo.Exists"/>.
/// </summary>
public bool Exists
{
get { return FileSystem.Exists(this); }
}
/// <summary>
/// True if the path is a rooted/fully qualified path. Otherwise returns false if it is a relative path.
/// Compatible with <see cref="System.IO.Path.IsPathRooted(string)"/>.
/// </summary>
public bool IsPathRooted
{
get
{
/* The IsPathRooted method returns true if the first character is a directory separator character such as "\", or if the path starts with a drive letter and colon (:).
* For example, it returns true for path strings such as "\\MyDir\\MyFile.txt", "C:\\MyDir", or "C: MyDir". It returns false for path strings such as "MyDir".
* - https://msdn.microsoft.com/en-us/library/system.io.path.ispathrooted%28v=vs.110%29.aspx
*/
var pathString = this.PathString;
bool rooted =
DirectorySeperatorChars.Any(c => c == pathString[0])
|| pathString.Length >= 2 && pathString[1] == ':';
return rooted;
}
}
/// <summary>
/// For code compatibility with <see cref="System.IO.FileInfo.CreateText()"/>
/// </summary>
public System.IO.StreamWriter CreateText()
{
var stream = FileSystem.CreateFile(this);
return new System.IO.StreamWriter(stream, System.Text.Encoding.UTF8);
}
/// <summary>
/// For code compatibility with <see cref="System.IO.Path.GetFileName(string)"/>
/// </summary>
public static string GetFileName(string path)
{
return BadPath.GetFileName(path);
}
}
}

View File

@@ -1,61 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace LessIO.Strategies
{
/// <summary>
/// See <see cref="FileSystem"/> for documentation of each method of this class.
/// </summary>
internal abstract class FileSystemStrategy
{
public abstract void SetLastWriteTime(Path path, DateTime lastWriteTime);
public abstract void SetAttributes(Path path, FileAttributes fileAttributes);
public abstract FileAttributes GetAttributes(Path path);
public abstract bool Exists(Path path);
public abstract void CreateDirectory(Path path);
public abstract void Copy(Path source, Path dest);
public abstract void RemoveDirectory(Path path, bool recursively);
public abstract void RemoveFile(Path path, bool force);
public abstract System.IO.Stream CreateFile(Path path);
public abstract IEnumerable<Path> ListContents(Path directory);
public virtual IEnumerable<Path> ListContents(Path directory, bool recursive)
{
IEnumerable<Path> children = ListContents(directory);
if (recursive)
{
IEnumerable<Path> grandChildren = children.SelectMany(
p => ListContents(p, recursive)
);
return Enumerable.Concat(children, grandChildren);
}
else
{
return children;
}
}
public virtual bool IsDirectory(Path path)
{
FileAttributes attributes = GetAttributes(path);
return (attributes & FileAttributes.Directory) == FileAttributes.Directory;
}
public virtual bool IsReadOnly(Path path)
{
FileAttributes attributes = GetAttributes(path);
return (attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly;
}
public virtual void SetReadOnly(Path path, bool readOnly)
{
FileAttributes attributes = GetAttributes(path);
if (readOnly)
attributes = attributes | FileAttributes.ReadOnly;
else
attributes = attributes & ~FileAttributes.ReadOnly;
SetAttributes(path, attributes);
}
}
}

View File

@@ -1,211 +0,0 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Runtime.InteropServices;
using FileAttributes = System.IO.FileAttributes;
namespace LessIO.Strategies.Win32
{
/// <summary>
/// Native/Win32 methods for accessing file system. Primarily to get around <see cref="System.IO.PathTooLongException"/>.
/// Good references:
/// * https://blogs.msdn.microsoft.com/bclteam/2007/03/26/long-paths-in-net-part-2-of-3-long-path-workarounds-kim-hamilton/
/// </summary>
internal class NativeMethods
{
/// <summary>
/// Specified in Windows Headers for default maximum path. To go beyond this length you must prepend <see cref="LongPathPrefix"/> to the path.
/// </summary>
internal const int MAX_PATH = 260;
/// <summary>
/// This is the special prefix to prepend to paths to support up to 32,767 character paths.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
/// </summary>
internal static readonly string LongPathPrefix = @"\\?\";
/// <summary>
/// This is the special prefix to prepend to UNC paths to support up to 32,767 character paths.
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx#maxpath
/// </summary>
internal static readonly string LongPathPrefixUNC = @"\\?\UNC\";
internal static IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
[StructLayout(LayoutKind.Sequential)]
internal class SECURITY_ATTRIBUTES
{
//internal unsafe byte* pSecurityDescriptor = (byte*)null;
internal IntPtr pSecurityDescriptor;
internal int nLength;
internal int bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
internal struct FILETIME
{
internal uint dwLowDateTime;
internal uint dwHighDateTime;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct WIN32_FIND_DATA
{
internal EFileAttributes dwFileAttributes;
internal FILETIME ftCreationTime;
internal FILETIME ftLastAccessTime;
internal FILETIME ftLastWriteTime;
internal int nFileSizeHigh;
internal int nFileSizeLow;
internal int dwReserved0;
internal int dwReserved1;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
internal string cFileName;
// not using this
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
internal string cAlternate;
}
[Flags]
internal enum EFileAccess : uint
{
FILE_READ_ATTRIBUTES = 0x00000080,
FILE_WRITE_ATTRIBUTES = 0x00000100,
GenericRead = 0x80000000,
GenericWrite = 0x40000000,
GenericExecute = 0x20000000,
GenericAll = 0x10000000
}
[Flags]
internal enum EFileShare : uint
{
None = 0x00000000,
Read = 0x00000001,
Write = 0x00000002,
Delete = 0x00000004
}
internal enum ECreationDisposition : uint
{
New = 1,
CreateAlways = 2,
OpenExisting = 3,
OpenAlways = 4,
TruncateExisting = 5
}
[Flags]
internal enum EFileAttributes : uint
{
None = 0x0,
Readonly = 0x00000001,
Hidden = 0x00000002,
System = 0x00000004,
Directory = 0x00000010,
Archive = 0x00000020,
Device = 0x00000040,
Normal = 0x00000080,
Temporary = 0x00000100,
SparseFile = 0x00000200,
ReparsePoint = 0x00000400,
Compressed = 0x00000800,
Offline = 0x00001000,
NotContentIndexed = 0x00002000,
Encrypted = 0x00004000,
Write_Through = 0x80000000,
Overlapped = 0x40000000,
NoBuffering = 0x20000000,
RandomAccess = 0x10000000,
SequentialScan = 0x08000000,
DeleteOnClose = 0x04000000,
BackupSemantics = 0x02000000,
PosixSemantics = 0x01000000,
OpenReparsePoint = 0x00200000,
OpenNoRecall = 0x00100000,
FirstPipeInstance = 0x00080000
}
/// <summary>
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363855%28v=vs.85%29.aspx
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
internal static extern bool CreateDirectory(string path, SECURITY_ATTRIBUTES lpSecurityAttributes);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern IntPtr FindFirstFile(string lpFileName, out
WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)]
internal static extern bool FindNextFile(IntPtr hFindFile, out
WIN32_FIND_DATA lpFindFileData);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern bool FindClose(IntPtr hFindFile);
/// <summary>
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365535%28v=vs.85%29.aspx
/// </summary>
[DllImport("kernel32.dll", EntryPoint = "SetFileAttributes", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
internal static extern bool SetFileAttributes(string lpFileName, uint dwFileAttributes);
/// <summary>
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364944%28v=vs.85%29.aspx
/// </summary>
/// <param name="lpFileName"></param>
/// <returns></returns>
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
internal static extern uint GetFileAttributes(string lpFileName);
// Invalid is from C:\Program Files (x86)\Windows Kits\8.1\Include\um\fileapi.h
//#define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
internal static readonly uint INVALID_FILE_ATTRIBUTES = 0xffffffff;
[DllImport("kernel32.dll", EntryPoint = "RemoveDirectory", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
internal static extern bool RemoveDirectory(string lpPathName);
[DllImport("kernel32.dll", EntryPoint = "DeleteFile", CharSet = CharSet.Auto, SetLastError = true, BestFitMapping = false)]
internal static extern bool DeleteFile(string path);
/// <summary>
/// See https://msdn.microsoft.com/en-us/library/windows/desktop/aa363858%28v=vs.85%29.aspx
/// </summary>
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
internal static extern SafeFileHandle CreateFile(string lpFileName,
EFileAccess dwDesiredAccess,
EFileShare dwShareMode,
IntPtr lpSecurityAttributes,
ECreationDisposition dwCreationDisposition,
EFileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
// This binding only allows setting creation and last write times.
// The last access time parameter must be zero; that time is not
// modified.
[DllImport("kernel32.dll", SetLastError = true)]
internal static extern bool SetFileTime(
IntPtr hFile,
ref long lpCreationTime,
IntPtr lpLastAccessTime,
ref long lpLastWriteTime);
/// <summary>
/// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851%28v=vs.85%29.aspx
/// </summary>
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
internal static extern bool CopyFile(string lpExistingFileName, string lpNewFileName, bool bFailIfExists);
internal const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;
/// <summary>
/// https://msdn.microsoft.com/en-us/library/windows/desktop/ms679351%28v=vs.85%29.aspx
/// </summary>
[DllImport("kernel32.dll")]
internal static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, [Out] System.Text.StringBuilder lpBuffer, uint nSize, IntPtr Arguments);
}
}

View File

@@ -1,230 +0,0 @@
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using BadPath = System.IO.Path;
namespace LessIO.Strategies.Win32
{
internal sealed class Win32FileSystemStrategy : FileSystemStrategy
{
public Win32FileSystemStrategy()
{
}
private static Exception CreateWin32LastErrorException(string userMessage, params object[] args)
{
uint lastError = (uint)Marshal.GetLastWin32Error();
const int bufferByteCapacity = 1024 * 32;
StringBuilder buffer = new StringBuilder(bufferByteCapacity);// NOTE: capacity here will be interpreted by StringBuilder as chars not bytes, but that's ok since it is definitely bigger in byte terms.
uint bufferCharLength = NativeMethods.FormatMessage(NativeMethods.FORMAT_MESSAGE_FROM_SYSTEM, IntPtr.Zero, lastError, 0, buffer, bufferByteCapacity, IntPtr.Zero);
string systemErrorMessage;
if (bufferCharLength == 0)
{ //FormatMessage failed:
systemErrorMessage = string.Format("Error code=0x{1:x8}", lastError);
}
else
{
Debug.Assert(bufferCharLength < buffer.Capacity, "unexpected result capacity");
char[] systemChars = new char[bufferCharLength];
buffer.CopyTo(0, systemChars, 0, (int)bufferCharLength);
systemErrorMessage = new string(systemChars);
}
var formattedUserMessage = string.Format(userMessage, args);
return new Exception(formattedUserMessage + " System error information:'" + systemErrorMessage + "'");
}
public override void SetLastWriteTime(Path path, DateTime lastWriteTime)
{
SafeFileHandle h = NativeMethods.CreateFile(
path.WithWin32LongPathPrefix(),
NativeMethods.EFileAccess.FILE_READ_ATTRIBUTES | NativeMethods.EFileAccess.FILE_WRITE_ATTRIBUTES,
NativeMethods.EFileShare.Read | NativeMethods.EFileShare.Write| NativeMethods.EFileShare.Delete,
IntPtr.Zero,// See https://github.com/activescott/LessIO/issues/4 before changing these flags.
NativeMethods.ECreationDisposition.OpenExisting,
NativeMethods.EFileAttributes.None,
IntPtr.Zero);
using (h)
{
if (h.IsInvalid)
throw CreateWin32LastErrorException("Error opening file {0}.", path);
var modifiedTime = lastWriteTime.ToFileTime();
if (!NativeMethods.SetFileTime(
h.DangerousGetHandle(),
ref modifiedTime, IntPtr.Zero, ref modifiedTime))
throw CreateWin32LastErrorException("Error setting times for {0}.", path);
}
}
public override void SetAttributes(Path path, FileAttributes fileAttributes)
{
var result = NativeMethods.SetFileAttributes(path.WithWin32LongPathPrefix(), (uint)fileAttributes);
if (!result)
{
throw CreateWin32LastErrorException("Error setting file attributes on '{0}'.", path);
}
}
public override FileAttributes GetAttributes(Path path)
{
var result = NativeMethods.GetFileAttributes(path.WithWin32LongPathPrefix());
if (result == NativeMethods.INVALID_FILE_ATTRIBUTES)
{
throw CreateWin32LastErrorException("Error getting file attributes for '{0}'.", path);
}
return (FileAttributes)result;
}
public override void CreateDirectory(Path path)
{
// Since System.IO.Directory.Create() creates all neecessary directories, we emulate here:
string pathString = path.PathString;
var dirsToCreate = new List<String>();
int lengthRoot = path.PathRoot.Length;
var firstNonRootPathIndex = pathString.IndexOfAny(Path.DirectorySeperatorChars, lengthRoot);
if (firstNonRootPathIndex == -1)
{
// this is a directory directly off of the root (because no, non-root directory seperators exist)
firstNonRootPathIndex = pathString.Length - 1; // set it to the whole path so that the loop below will create the root dir
}
var i = firstNonRootPathIndex;
while (i < pathString.Length)
{
if (Path.IsDirectorySeparator(pathString[i]) || i == pathString.Length - 1)
{
Path currentPath = new Path(pathString.Substring(0, i + 1));
if (!Exists(currentPath))
{
bool succeeded = NativeMethods.CreateDirectory(currentPath.WithWin32LongPathPrefix(), null);
if (!succeeded)
throw CreateWin32LastErrorException("Error creating directory '{0}'.", currentPath);
}
Debug.Assert(Exists(currentPath), "path should always exists at this point!");
}
i++;
}
}
public override bool Exists(Path path)
{
var result = NativeMethods.GetFileAttributes(path.WithWin32LongPathPrefix());
return (result != NativeMethods.INVALID_FILE_ATTRIBUTES);
}
public override void RemoveDirectory(Path path, bool recursively)
{
if (recursively)
{ // first gather contents and remove all content
RemoveDirectoryRecursively(path);
}
else
{
var succeeded = NativeMethods.RemoveDirectory(path.WithWin32LongPathPrefix());
if (!succeeded)
throw CreateWin32LastErrorException("Error removing directory '{0}'.", path);
}
}
private void RemoveDirectoryRecursively(Path dirName)
{
var list = ListContents(dirName, true).ToArray();
/*
We need to delete leaf nodes in the file hierarchy first to make sure all directories are empty.
We identify leaf nodes by their depth (greatest number of path parts)
*/
Func<Path,int> pathPartCount = (Path p) => p.PathString.Split(Path.DirectorySeperatorChars).Length;
Array.Sort<Path>(list, (a,b) => pathPartCount(b) - pathPartCount(a));
Array.ForEach(list
, p => {
if (FileSystem.IsDirectory(p))
FileSystem.RemoveDirectory(p);
else
FileSystem.RemoveFile(p, true);
Debug.Assert(FileSystem.Exists(p) == false, string.Format("Files/directory still exits:'{0}'", p));
}
);
Debug.Assert(list.All(p => FileSystem.Exists(p) == false), "Some files/directories still exits?");
FileSystem.RemoveDirectory(dirName, false);
}
public override void RemoveFile(Path path, bool force)
{
if (IsReadOnly(path) && force)
SetReadOnly(path, false);
var succeeded = NativeMethods.DeleteFile(path.WithWin32LongPathPrefix());
if (!succeeded)
throw CreateWin32LastErrorException("Error deleting file '{0}'.", path);
}
public override void Copy(Path source, Path dest)
{
var succeeded = NativeMethods.CopyFile(source.WithWin32LongPathPrefix(), dest.WithWin32LongPathPrefix(), true);
if (!succeeded)
throw CreateWin32LastErrorException("Error copying file '{0}' to '{1}'.", source, dest);
}
/// <summary>
/// Creates or overwrites the file at the specified path.
/// </summary>
/// <param name="path">The path and name of the file to create. Supports long file paths.</param>
/// <returns>A <see cref="System.IO.Stream"/> that provides read/write access to the file specified in path.</returns>
public override System.IO.Stream CreateFile(Path path)
{
if (path.IsEmpty)
throw new ArgumentNullException("path");
NativeMethods.EFileAccess fileAccess = NativeMethods.EFileAccess.GenericWrite | NativeMethods.EFileAccess.GenericRead;
NativeMethods.EFileShare fileShareMode = NativeMethods.EFileShare.None;//exclusive
NativeMethods.ECreationDisposition creationDisposition = NativeMethods.ECreationDisposition.CreateAlways;
SafeFileHandle hFile = NativeMethods.CreateFile(path.WithWin32LongPathPrefix(), fileAccess, fileShareMode, IntPtr.Zero, creationDisposition, NativeMethods.EFileAttributes.Normal, IntPtr.Zero);
if (hFile.IsInvalid)
throw CreateWin32LastErrorException("Error creating file at path '{0}'.", path);
return new System.IO.FileStream(hFile, System.IO.FileAccess.ReadWrite);
}
public override IEnumerable<Path> ListContents(Path directory)
{
//NOTE: An important part of our contract is that if directory is not a directory, we return an empty set:
if (!Exists(directory) || !IsDirectory(directory))
yield break;
// normalize dirName so we can assume it doesn't have a slash on the end:
string dirName = directory.PathString;
dirName = dirName.TrimEnd(Path.DirectorySeperatorChars);
dirName = new Path(dirName).WithWin32LongPathPrefix();
NativeMethods.WIN32_FIND_DATA findData;
IntPtr findHandle = NativeMethods.FindFirstFile(dirName + @"\*", out findData);
if (findHandle != NativeMethods.INVALID_HANDLE_VALUE)
{
try
{
bool found;
do
{
string currentFileName = findData.cFileName;
if (currentFileName != "." && currentFileName != "..")
{
//NOTE: Instantiating the Path here will strip off the Win32 prefix that is added here as needed:
yield return Path.Combine(dirName, currentFileName);
}
// find next
found = NativeMethods.FindNextFile(findHandle, out findData);
} while (found);
}
finally
{
NativeMethods.FindClose(findHandle);
}
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net48;netcoreapp3.1;net5.0-windows</TargetFrameworks>
<TargetFrameworks>net48;netcoreapp3.1;net5.0</TargetFrameworks>
<PlatformTarget>x86</PlatformTarget>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<OutputType>Exe</OutputType>

50
appveyor.yml Normal file
View File

@@ -0,0 +1,50 @@
# version format
version: 1.5.1-{build}
# pull request template
pull_requests:
do_not_increment_build_number: true
# vm template
image: Visual Studio 2019
# environment variables
environment:
EnableNuGetPackageRestore: true
# msbuild configuration
platform:
- Any CPU
configuration:
- Debug
# install dependencies
install:
- ps: appveyor DownloadFile https://dist.nuget.org/win-x86-commandline/latest/nuget.exe
- cd %APPVEYOR_BUILD_FOLDER%
- git submodule update --init --recursive
# pre-build script
before_build:
- nuget restore
# build step
build:
verbosity: minimal
project: BurnOutSharp.sln
# post-build step
after_build:
- cd Test\bin\Debug
- 7z a BurnOutSharp_net48.zip net48\*
- 7z a BurnOutSharp_netcoreapp3.1.zip netcoreapp3.1\*
- 7z a BurnOutSharp_net5.0.zip net5.0\*
# artifact linking
artifacts:
- path: Test\bin\Debug\BurnOutSharp_net48.zip
name: BurnOutSharp (.NET Framework 4.8)
- path: Test\bin\Debug\BurnOutSharp_netcoreapp3.1.zip
name: BurnOutSharp (.NET Core 3.1)
- path: Test\bin\Debug\BurnOutSharp_net5.0.zip
name: BurnOutSharp (.NET 5.0)