mirror of
https://github.com/SabreTools/BinaryObjectScanner.git
synced 2026-04-17 11:42:40 +00:00
Replace LessIO code mirror with submodule
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "BurnOutSharp/External/LessIO"]
|
||||
path = BurnOutSharp/External/LessIO
|
||||
url = https://github.com/activescott/LessIO.git
|
||||
@@ -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
1
BurnOutSharp/External/LessIO
vendored
Submodule
Submodule BurnOutSharp/External/LessIO added at 1def7d19cc
@@ -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
|
||||
*/
|
||||
}
|
||||
}
|
||||
160
BurnOutSharp/External/LessIO.bak/FileSystem.cs
vendored
160
BurnOutSharp/External/LessIO.bak/FileSystem.cs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
399
BurnOutSharp/External/LessIO.bak/Path.cs
vendored
399
BurnOutSharp/External/LessIO.bak/Path.cs
vendored
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
50
appveyor.yml
Normal 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)
|
||||
Reference in New Issue
Block a user