You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

643 lines
26 KiB

/* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
namespace Alphaleonis.Win32.Filesystem
{
/// <summary>Provides the base class for both <see cref="FileInfo"/> and <see cref="DirectoryInfo"/> objects.</summary>
[SerializableAttribute]
[ComVisibleAttribute(true)]
public abstract class FileSystemInfo : MarshalByRefObject
{
#region Methods
#region .NET
#region Delete
/// <summary>Deletes a file or directory.</summary>
[SecurityCritical]
public abstract void Delete();
#endregion // Delete
#region Refresh
/// <summary>Refreshes the state of the object.</summary>
/// <remarks>
/// <para>FileSystemInfo.Refresh() takes a snapshot of the file from the current file system.</para>
/// <para>Refresh cannot correct the underlying file system even if the file system returns incorrect or outdated information.</para>
/// <para>This can happen on platforms such as Windows 98.</para>
/// <para>Calls must be made to Refresh() before attempting to get the attribute information, or the information will be
/// outdated.</para>
/// </remarks>
[SecurityCritical]
protected void Refresh()
{
DataInitialised = File.FillAttributeInfoCore(Transaction, LongFullName, ref Win32AttributeData, false, false);
}
#endregion // Refresh
#region ToString
/// <summary>Returns a string that represents the current object.</summary>
/// <remarks>
/// ToString is the major formatting method in the .NET Framework. It converts an object to its string representation so that it is
/// suitable for display.
/// </remarks>
/// <returns>A string that represents this instance.</returns>
public override string ToString()
{
// "Alphaleonis.Win32.Filesystem.FileSystemInfo"
return GetType().ToString();
}
#endregion // ToString
#region Equality
/// <summary>Determines whether the specified Object is equal to the current Object.</summary>
/// <param name="obj">Another object to compare to.</param>
/// <returns><see langword="true"/> if the specified Object is equal to the current Object; otherwise, <see langword="false"/>.</returns>
public override bool Equals(object obj)
{
if (obj == null || GetType() != obj.GetType())
return false;
FileSystemInfo other = obj as FileSystemInfo;
return other != null && (other.Name != null &&
(other.FullName.Equals(FullName, StringComparison.OrdinalIgnoreCase) &&
other.Attributes.Equals(Attributes) &&
other.CreationTimeUtc.Equals(CreationTimeUtc) &&
other.LastWriteTimeUtc.Equals(LastWriteTimeUtc)));
}
// A random prime number will be picked and added to the HashCode, each time an instance is created.
[NonSerialized]
private readonly int _random = new Random().Next(0, 19);
[NonSerialized]
private static readonly int[] Primes = { 17, 23, 29, 37, 47, 59, 71, 89, 107, 131, 163, 197, 239, 293, 353, 431, 521, 631, 761, 919 };
/// <summary>Serves as a hash function for a particular type.</summary>
/// <returns>A hash code for the current Object.</returns>
public override int GetHashCode()
{
string fullName = FullName;
string name = Name;
unchecked
{
int hash = Primes[_random];
if (!Utils.IsNullOrWhiteSpace(fullName))
hash = hash * Primes[1] + fullName.GetHashCode();
if (!Utils.IsNullOrWhiteSpace(name))
hash = hash * Primes[1] + name.GetHashCode();
hash = hash * Primes[1] + Attributes.GetHashCode();
hash = hash * Primes[1] + CreationTimeUtc.GetHashCode();
hash = hash * Primes[1] + LastWriteTimeUtc.GetHashCode();
return hash;
}
}
/// <summary>Implements the operator ==</summary>
/// <param name="left">A.</param>
/// <param name="right">B.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(FileSystemInfo left, FileSystemInfo right)
{
return ReferenceEquals(left, null) && ReferenceEquals(right, null) ||
!ReferenceEquals(left, null) && !ReferenceEquals(right, null) && left.Equals(right);
}
/// <summary>Implements the operator !=</summary>
/// <param name="left">A.</param>
/// <param name="right">B.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(FileSystemInfo left, FileSystemInfo right)
{
return !(left == right);
}
#endregion // Equality
#endregion // .NET
#region AlphaFS
#region RefreshEntryInfo
/// <summary>Refreshes the state of the <see cref="FileSystemEntryInfo"/> EntryInfo instance.</summary>
/// <remarks>
/// <para>FileSystemInfo.RefreshEntryInfo() takes a snapshot of the file from the current file system.</para>
/// <para>Refresh cannot correct the underlying file system even if the file system returns incorrect or outdated information.</para>
/// <para>This can happen on platforms such as Windows 98.</para>
/// <para>Calls must be made to Refresh() before attempting to get the attribute information, or the information will be outdated.</para>
/// </remarks>
[SecurityCritical]
protected void RefreshEntryInfo()
{
_entryInfo = File.GetFileSystemEntryInfoCore(IsDirectory, Transaction, LongFullName, true, PathFormat.LongFullPath);
if (_entryInfo == null)
DataInitialised = -1;
else
{
DataInitialised = 0;
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA(_entryInfo.Win32FindData);
}
}
#endregion // RefreshEntryInfo
#region Reset
/// <summary>[AlphaFS] Resets the state of the file system object to uninitialized.</summary>
internal void Reset()
{
DataInitialised = -1;
}
#endregion // Reset
#region InitializeCore
/// <summary>Initializes the specified file name.</summary>
/// <exception cref="ArgumentException"/>
/// <exception cref="NotSupportedException"/>
/// <param name="isFolder">Specifies that <paramref name="path"/> is a file or directory.</param>
/// <param name="transaction">The transaction.</param>
/// <param name="path">The full path and name of the file.</param>
/// <param name="pathFormat">Indicates the format of the path parameter(s).</param>
internal void InitializeCore(bool isFolder, KernelTransaction transaction, string path, PathFormat pathFormat)
{
if (pathFormat == PathFormat.RelativePath)
Path.CheckSupportedPathFormat(path, true, true);
LongFullName = Path.GetExtendedLengthPathCore(transaction, path, pathFormat, GetFullPathOptions.TrimEnd | (isFolder ? GetFullPathOptions.RemoveTrailingDirectorySeparator : 0) | GetFullPathOptions.ContinueOnNonExist);
// (Not on MSDN): .NET 4+ Trailing spaces are removed from the end of the path parameter before creating the FileSystemInfo instance.
FullPath = Path.GetRegularPathCore(LongFullName, GetFullPathOptions.None, false);
IsDirectory = isFolder;
Transaction = transaction;
OriginalPath = FullPath.Length == 2 && (FullPath[1] == Path.VolumeSeparatorChar)
? Path.CurrentDirectoryPrefix
: path;
DisplayPath = OriginalPath.Length != 2 || OriginalPath[1] != Path.VolumeSeparatorChar
? Path.GetRegularPathCore(OriginalPath, GetFullPathOptions.None, false)
: Path.CurrentDirectoryPrefix;
}
#endregion // InitializeCore
#endregion // AlphaFS
#endregion // Methods
#region Properties
#region .NET
#region Attributes
/// <summary>
/// Gets or sets the attributes for the current file or directory.
/// </summary>
/// <remarks>
/// <para>The value of the CreationTime property is pre-cached</para>
/// <para>To get the latest value, call the Refresh method.</para>
/// </remarks>
/// <value><see cref="FileAttributes"/> of the current <see cref="FileSystemInfo"/>.</value>
///
/// <exception cref="FileNotFoundException"/>
/// <exception cref="DirectoryNotFoundException"/>
/// <exception cref="IOException"/>
public FileAttributes Attributes
{
[SecurityCritical]
get
{
if (DataInitialised == -1)
{
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
Refresh();
}
// MSDN: .NET 3.5+: IOException: Refresh cannot initialize the data.
if (DataInitialised != 0)
NativeError.ThrowException(DataInitialised, LongFullName);
return Win32AttributeData.dwFileAttributes;
}
[SecurityCritical]
set
{
File.SetAttributesCore(IsDirectory, Transaction, LongFullName, value, PathFormat.LongFullPath);
Reset();
}
}
#endregion // Attributes
#region CreationTime
/// <summary>Gets or sets the creation time of the current file or directory.</summary>
/// <remarks>
/// <para>The value of the CreationTime property is pre-cached To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions whose values may not be continuously updated by
/// the operating system.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return
/// 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.</para>
/// <para>NTFS-formatted drives may cache file meta-info, such as file creation time, for a short period of time.
/// This process is known as file tunneling. As a result, it may be necessary to explicitly set the creation time of a file if you are
/// overwriting or replacing an existing file.</para>
/// </remarks>
/// <value>The creation date and time of the current <see cref="FileSystemInfo"/> object.</value>
///
/// <exception cref="DirectoryNotFoundException"/>
/// <exception cref="IOException"/>
public DateTime CreationTime
{
[SecurityCritical] get { return CreationTimeUtc.ToLocalTime(); }
[SecurityCritical] set { CreationTimeUtc = value.ToUniversalTime(); }
}
#endregion // CreationTime
#region CreationTimeUtc
/// <summary>Gets or sets the creation time, in coordinated universal time (UTC), of the current file or directory.</summary>
/// <remarks>
/// <para>The value of the CreationTimeUtc property is pre-cached
/// To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions
/// whose values may not be continuously updated by the operating system.</para>
/// <para>To get the latest value, call the Refresh method.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return
/// 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC).</para>
/// <para>NTFS-formatted drives may cache file meta-info, such as file creation time, for a short period of time.
/// This process is known as file tunneling. As a result, it may be necessary to explicitly set the creation time
/// of a file if you are overwriting or replacing an existing file.</para>
/// </remarks>
/// <value>The creation date and time in UTC format of the current <see cref="FileSystemInfo"/> object.</value>
///
/// <exception cref="DirectoryNotFoundException"/>
/// <exception cref="IOException"/>
[ComVisible(false)]
public DateTime CreationTimeUtc
{
[SecurityCritical]
get
{
if (DataInitialised == -1)
{
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
Refresh();
}
// MSDN: .NET 3.5+: IOException: Refresh cannot initialize the data.
if (DataInitialised != 0)
NativeError.ThrowException(DataInitialised, LongFullName);
return DateTime.FromFileTimeUtc(Win32AttributeData.ftCreationTime);
}
[SecurityCritical]
set
{
File.SetFsoDateTimeCore(IsDirectory, Transaction, LongFullName, value, null, null, false, PathFormat.LongFullPath);
Reset();
}
}
#endregion // CreationTimeUtc
#region Exists
/// <summary>
/// Gets a value indicating whether the file or directory exists.
/// </summary>
/// <remarks>
/// <para>The <see cref="Exists"/> property returns <see langword="false"/> if any error occurs while trying to determine if the
/// specified file or directory exists.</para>
/// <para>This can occur in situations that raise exceptions such as passing a directory- or file name with invalid characters or too
/// many characters,</para>
/// <para>a failing or missing disk, or if the caller does not have permission to read the file or directory.</para>
/// </remarks>
/// <value><see langword="true"/> if the file or directory exists; otherwise, <see langword="false"/>.</value>
public abstract bool Exists { get; }
#endregion // Exists
#region Extension
/// <summary>
/// Gets the string representing the extension part of the file.
/// </summary>
/// <remarks>
/// <para>The Extension property returns the <see cref="FileSystemInfo"/> extension, including the period (.).</para>
/// <para>For example, for a file c:\NewFile.txt, this property returns ".txt".</para>
/// </remarks>
/// <value>A string containing the <see cref="FileSystemInfo"/> extension.</value>
public string Extension
{
get { return Path.GetExtension(FullPath, false); }
}
#endregion // Extension
#region FullName
/// <summary>
/// Gets the full path of the directory or file.
/// </summary>
/// <value>A string containing the full path.</value>
public virtual string FullName
{
[SecurityCritical]
get { return FullPath; }
}
#endregion // FullName
#region LastAccessTime
/// <summary>Gets or sets the time the current file or directory was last accessed.</summary>
/// <remarks>
/// <para>The value of the LastAccessTime property is pre-cached
/// To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions
/// whose values may not be continuously updated by the operating system.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return
/// 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.</para>
/// </remarks>
/// <value>The time that the current file or directory was last accessed.</value>
///
/// <exception cref="IOException"/>
public DateTime LastAccessTime
{
[SecurityCritical] get { return LastAccessTimeUtc.ToLocalTime(); }
[SecurityCritical] set { LastAccessTimeUtc = value.ToUniversalTime(); }
}
#endregion // LastAccessTime
#region LastAccessTimeUtc
/// <summary>Gets or sets the time, in coordinated universal time (UTC), that the current file or directory was last accessed.</summary>
/// <remarks>
/// <para>The value of the LastAccessTimeUtc property is pre-cached.
/// To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions
/// whose values may not be continuously updated by the operating system.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return
/// 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.</para>
/// </remarks>
/// <value>The UTC time that the current file or directory was last accessed.</value>
///
/// <exception cref="IOException"/>
[ComVisible(false)]
public DateTime LastAccessTimeUtc
{
[SecurityCritical]
get
{
if (DataInitialised == -1)
{
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
Refresh();
}
// MSDN: .NET 3.5+: IOException: Refresh cannot initialize the data.
if (DataInitialised != 0)
NativeError.ThrowException(DataInitialised, LongFullName);
return DateTime.FromFileTimeUtc(Win32AttributeData.ftLastAccessTime);
}
[SecurityCritical]
set
{
File.SetFsoDateTimeCore(IsDirectory, Transaction, LongFullName, null, value, null, false, PathFormat.LongFullPath);
Reset();
}
}
#endregion // LastAccessTimeUtc
#region LastWriteTime
/// <summary>Gets or sets the time when the current file or directory was last written to.</summary>
/// <remarks>
/// <para>The value of the LastWriteTime property is pre-cached.
/// To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions
/// whose values may not be continuously updated by the operating system.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return
/// 12:00 midnight, January 1, 1601 A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.</para>
/// </remarks>
/// <value>The time the current file was last written.</value>
///
/// <exception cref="IOException"/>
public DateTime LastWriteTime
{
get { return LastWriteTimeUtc.ToLocalTime(); }
set { LastWriteTimeUtc = value.ToUniversalTime(); }
}
#endregion // LastWriteTime
#region LastWriteTimeUtc
/// <summary>Gets or sets the time, in coordinated universal time (UTC), when the current file or directory was last written to.</summary>
/// <remarks>
/// <para>The value of the LastWriteTimeUtc property is pre-cached. To get the latest value, call the Refresh method.</para>
/// <para>This method may return an inaccurate value, because it uses native functions whose values may not be continuously updated by
/// the operating system.</para>
/// <para>If the file described in the FileSystemInfo object does not exist, this property will return 12:00 midnight, January 1, 1601
/// A.D. (C.E.) Coordinated Universal Time (UTC), adjusted to local time.</para>
/// </remarks>
/// <value>The UTC time when the current file was last written to.</value>
[ComVisible(false)]
public DateTime LastWriteTimeUtc
{
[SecurityCritical]
get
{
if (DataInitialised == -1)
{
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
Refresh();
}
// MSDN: .NET 3.5+: IOException: Refresh cannot initialize the data.
if (DataInitialised != 0)
NativeError.ThrowException(DataInitialised, LongFullName);
return DateTime.FromFileTimeUtc(Win32AttributeData.ftLastWriteTime);
}
[SecurityCritical]
set
{
File.SetFsoDateTimeCore(IsDirectory, Transaction, LongFullName, null, null, value, false, PathFormat.LongFullPath);
Reset();
}
}
#endregion // LastWriteTimeUtc
#region Name
/// <summary>
/// For files, gets the name of the file. For directories, gets the name of the last directory in the hierarchy if a hierarchy exists.
/// <para>Otherwise, the Name property gets the name of the directory.</para>
/// </summary>
/// <remarks>
/// <para>For a directory, Name returns only the name of the parent directory, such as Dir, not c:\Dir.</para>
/// <para>For a subdirectory, Name returns only the name of the subdirectory, such as Sub1, not c:\Dir\Sub1.</para>
/// <para>For a file, Name returns only the file name and file name extension, such as MyFile.txt, not c:\Dir\Myfile.txt.</para>
/// </remarks>
/// <value>
/// <para>A string that is the name of the parent directory, the name of the last directory in the hierarchy,</para>
/// <para>or the name of a file, including the file name extension.</para>
/// </value>
public abstract string Name { get; }
#endregion // Name
#endregion // .NET
#region AlphaFS
#region DisplayPath
/// <summary>Returns the path as a string.</summary>
protected internal string DisplayPath { get; protected set; }
#endregion // DisplayPath
#region EntryInfo
private FileSystemEntryInfo _entryInfo;
/// <summary>[AlphaFS] Gets the instance of the <see cref="FileSystemEntryInfo"/> class.</summary>
public FileSystemEntryInfo EntryInfo
{
[SecurityCritical]
get
{
if (_entryInfo == null)
{
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA();
RefreshEntryInfo();
}
// MSDN: .NET 3.5+: IOException: Refresh cannot initialize the data.
if (DataInitialised > 0)
NativeError.ThrowException(DataInitialised, LongFullName);
return _entryInfo;
}
internal set
{
_entryInfo = value;
DataInitialised = value == null ? -1 : 0;
if (DataInitialised == 0)
Win32AttributeData = new NativeMethods.WIN32_FILE_ATTRIBUTE_DATA(_entryInfo.Win32FindData);
}
}
#endregion // EntryInfo
#region IsDirectory
/// <summary>[AlphaFS] The initial "IsDirectory" indicator that was passed to the constructor.</summary>
protected bool IsDirectory { get; set; }
#endregion // IsDirectory
#region LongFullName
/// <summary>The full path of the file system object in Unicode (LongPath) format.</summary>
protected string LongFullName { get; set; }
#endregion // LongFullName
#region Transaction
/// <summary>[AlphaFS] Represents the KernelTransaction that was passed to the constructor.</summary>
protected KernelTransaction Transaction { get; set; }
#endregion // Transaction
#endregion // AlphaFS
#endregion // Properties
#region Fields
// We use this field in conjunction with the Refresh methods, if we succeed
// we store a zero, on failure we store the HResult in it so that we can
// give back a generic error back.
[NonSerialized] internal int DataInitialised = -1;
// The pre-cached FileSystemInfo information.
[NonSerialized] internal NativeMethods.WIN32_FILE_ATTRIBUTE_DATA Win32AttributeData;
#region .NET
/// <summary>Represents the fully qualified path of the file or directory.</summary>
/// <remarks>
/// <para>Classes derived from <see cref="FileSystemInfo"/> can use the FullPath field</para>
/// <para>to determine the full path of the object being manipulated.</para>
/// </remarks>
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected string FullPath;
/// <summary>The path originally specified by the user, whether relative or absolute.</summary>
[SuppressMessage("Microsoft.Design", "CA1051:DoNotDeclareVisibleInstanceFields")]
protected string OriginalPath;
#endregion // .NET
#endregion // Fields
}
}