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.
 
 

423 lines
16 KiB

  1. /* Copyright (C) 2008-2016 Peter Palotas, Jeffrey Jangli, Alexandr Normuradov
  2. *
  3. * Permission is hereby granted, free of charge, to any person obtaining a copy
  4. * of this software and associated documentation files (the "Software"), to deal
  5. * in the Software without restriction, including without limitation the rights
  6. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. * copies of the Software, and to permit persons to whom the Software is
  8. * furnished to do so, subject to the following conditions:
  9. *
  10. * The above copyright notice and this permission notice shall be included in
  11. * all copies or substantial portions of the Software.
  12. *
  13. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. * THE SOFTWARE.
  20. */
  21. using System;
  22. using System.Collections.Generic;
  23. using System.Diagnostics.CodeAnalysis;
  24. using System.IO;
  25. using System.Linq;
  26. using System.Security;
  27. namespace Alphaleonis.Win32.Filesystem
  28. {
  29. /// <summary>Provides access to information on a local or remote drive.</summary>
  30. /// <remarks>
  31. /// This class models a drive and provides methods and properties to query for drive information.
  32. /// Use DriveInfo to determine what drives are available, and what type of drives they are.
  33. /// You can also query to determine the capacity and available free space on the drive.
  34. /// </remarks>
  35. [Serializable]
  36. [SecurityCritical]
  37. public sealed class DriveInfo
  38. {
  39. #region Private Fields
  40. [NonSerialized]
  41. private readonly VolumeInfo _volumeInfo;
  42. [NonSerialized]
  43. private readonly DiskSpaceInfo _dsi;
  44. [NonSerialized]
  45. private bool _initDsie;
  46. [NonSerialized]
  47. private DriveType? _driveType;
  48. [NonSerialized]
  49. private string _dosDeviceName;
  50. [NonSerialized]
  51. private DirectoryInfo _rootDirectory;
  52. private readonly string _name;
  53. #endregion
  54. #region Constructors
  55. /// <summary>Provides access to information on the specified drive.</summary>
  56. /// <exception cref="ArgumentNullException"/>
  57. /// <exception cref="ArgumentException"/>
  58. /// <param name="driveName">
  59. /// A valid drive path or drive letter.
  60. /// <para>This can be either uppercase or lowercase,</para>
  61. /// <para>'a' to 'z' or a network share in the format: \\server\share</para>
  62. /// </param>
  63. [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Utils.IsNullOrWhiteSpace validates arguments.")]
  64. [SecurityCritical]
  65. public DriveInfo(string driveName)
  66. {
  67. if (Utils.IsNullOrWhiteSpace(driveName))
  68. throw new ArgumentNullException("driveName");
  69. if (driveName.Length == 1)
  70. _name += Path.VolumeSeparatorChar;
  71. else
  72. _name = Path.GetPathRoot(driveName, false);
  73. if (Utils.IsNullOrWhiteSpace(_name))
  74. throw new ArgumentException("Argument must be a drive letter (\"C\"), RootDir (\"C:\\\") or UNC path (\"\\\\server\\share\")");
  75. // If an exception is thrown, the original drivePath is used.
  76. _name = Path.AddTrailingDirectorySeparator(_name, false);
  77. // Initiate VolumeInfo() lazyload instance.
  78. _volumeInfo = new VolumeInfo(_name, false, true);
  79. // Initiate DiskSpaceInfo() lazyload instance.
  80. _dsi = new DiskSpaceInfo(_name, null, false, true);
  81. }
  82. #endregion // Constructors
  83. #region Properties
  84. /// <summary>Indicates the amount of available free space on a drive.</summary>
  85. /// <returns>The amount of free space available on the drive, in bytes.</returns>
  86. /// <remarks>This property indicates the amount of free space available on the drive. Note that this number may be different from the <see cref="TotalFreeSpace"/> number because this property takes into account disk quotas.</remarks>
  87. public long AvailableFreeSpace
  88. {
  89. get
  90. {
  91. GetDeviceInfo(3, 0);
  92. return _dsi == null ? 0 : _dsi.FreeBytesAvailable;
  93. }
  94. }
  95. /// <summary>Gets the name of the file system, such as NTFS or FAT32.</summary>
  96. /// <remarks>Use DriveFormat to determine what formatting a drive uses.</remarks>
  97. public string DriveFormat
  98. {
  99. get { return (string)GetDeviceInfo(0, 1); }
  100. }
  101. /// <summary>Gets the drive type.</summary>
  102. /// <returns>One of the <see cref="System.IO.DriveType"/> values.</returns>
  103. /// <remarks>
  104. /// The DriveType property indicates whether a drive is any of: CDRom, Fixed, Unknown, Network, NoRootDirectory,
  105. /// Ram, Removable, or Unknown. Values are listed in the <see cref="System.IO.DriveType"/> enumeration.
  106. /// </remarks>
  107. public DriveType DriveType
  108. {
  109. get { return (DriveType)GetDeviceInfo(2, 0); }
  110. }
  111. /// <summary>Gets a value indicating whether a drive is ready.</summary>
  112. /// <returns><see langword="true"/> if the drive is ready; otherwise, <see langword="false"/>.</returns>
  113. /// <remarks>
  114. /// IsReady indicates whether a drive is ready. For example, it indicates whether a CD is in a CD drive or whether
  115. /// a removable storage device is ready for read/write operations. If you do not test whether a drive is ready, and
  116. /// it is not ready, querying the drive using DriveInfo will raise an IOException.
  117. ///
  118. /// Do not rely on IsReady() to avoid catching exceptions from other members such as TotalSize, TotalFreeSpace, and DriveFormat.
  119. /// Between the time that your code checks IsReady and then accesses one of the other properties
  120. /// (even if the access occurs immediately after the check), a drive may have been disconnected or a disk may have been removed.
  121. /// </remarks>
  122. public bool IsReady
  123. {
  124. get { return File.ExistsCore(true, null, Name, PathFormat.LongFullPath); }
  125. }
  126. /// <summary>Gets the name of the drive.</summary>
  127. /// <returns>The name of the drive.</returns>
  128. /// <remarks>This property is the name assigned to the drive, such as C:\ or E:\</remarks>
  129. public string Name
  130. {
  131. get { return _name; }
  132. }
  133. /// <summary>Gets the root directory of a drive.</summary>
  134. /// <returns>A DirectoryInfo object that contains the root directory of the drive.</returns>
  135. public DirectoryInfo RootDirectory
  136. {
  137. get { return (DirectoryInfo)GetDeviceInfo(2, 1); }
  138. }
  139. /// <summary>Gets the total amount of free space available on a drive.</summary>
  140. /// <returns>The total free space available on a drive, in bytes.</returns>
  141. /// <remarks>This property indicates the total amount of free space available on the drive, not just what is available to the current user.</remarks>
  142. public long TotalFreeSpace
  143. {
  144. get
  145. {
  146. GetDeviceInfo(3, 0);
  147. return _dsi == null ? 0 : _dsi.TotalNumberOfFreeBytes;
  148. }
  149. }
  150. /// <summary>Gets the total size of storage space on a drive.</summary>
  151. /// <returns>The total size of the drive, in bytes.</returns>
  152. /// <remarks>This property indicates the total size of the drive in bytes, not just what is available to the current user.</remarks>
  153. public long TotalSize
  154. {
  155. get
  156. {
  157. GetDeviceInfo(3, 0);
  158. return _dsi == null ? 0 : _dsi.TotalNumberOfBytes;
  159. }
  160. }
  161. /// <summary>Gets or sets the volume label of a drive.</summary>
  162. /// <returns>The volume label.</returns>
  163. /// <remarks>
  164. /// The label length is determined by the operating system. For example, NTFS allows a volume label
  165. /// to be up to 32 characters long. Note that <see langword="null"/> is a valid VolumeLabel.
  166. /// </remarks>
  167. public string VolumeLabel
  168. {
  169. get { return (string)GetDeviceInfo(0, 2); }
  170. set { Volume.SetVolumeLabel(Name, value); }
  171. }
  172. /// <summary>[AlphaFS] Returns the <see ref="Alphaleonis.Win32.Filesystem.DiskSpaceInfo"/> instance.</summary>
  173. public DiskSpaceInfo DiskSpaceInfo
  174. {
  175. get
  176. {
  177. GetDeviceInfo(3, 0);
  178. return _dsi;
  179. }
  180. }
  181. /// <summary>[AlphaFS] The MS-DOS device name.</summary>
  182. public string DosDeviceName
  183. {
  184. get { return (string)GetDeviceInfo(1, 0); }
  185. }
  186. /// <summary>[AlphaFS] Indicates if this drive is a SUBST.EXE / DefineDosDevice drive mapping.</summary>
  187. public bool IsDosDeviceSubstitute
  188. {
  189. get { return !Utils.IsNullOrWhiteSpace(DosDeviceName) && DosDeviceName.StartsWith(Path.SubstitutePrefix, StringComparison.OrdinalIgnoreCase); }
  190. }
  191. /// <summary>[AlphaFS] Indicates if this drive is a UNC path.</summary>
  192. /// <remarks>Only retrieve this information if we're dealing with a real network share mapping: http://alphafs.codeplex.com/discussions/316583</remarks>
  193. public bool IsUnc
  194. {
  195. get { return !IsDosDeviceSubstitute && DriveType == DriveType.Network; }
  196. }
  197. /// <summary>[AlphaFS] Determines whether the specified volume name is a defined volume on the current computer.</summary>
  198. public bool IsVolume
  199. {
  200. get { return GetDeviceInfo(0, 0) != null; }
  201. }
  202. /// <summary>[AlphaFS] Contains information about a file-system volume.</summary>
  203. /// <returns>A VolumeInfo object that contains file-system volume information of the drive.</returns>
  204. public VolumeInfo VolumeInfo
  205. {
  206. get { return (VolumeInfo)GetDeviceInfo(0, 0); }
  207. }
  208. #endregion // Properties
  209. #region Methods
  210. /// <summary>Retrieves the drive names of all logical drives on a computer.</summary>
  211. /// <returns>An array of type <see cref="Alphaleonis.Win32.Filesystem.DriveInfo"/> that represents the logical drives on a computer.</returns>
  212. [SecurityCritical]
  213. public static DriveInfo[] GetDrives()
  214. {
  215. return Directory.EnumerateLogicalDrivesCore(false, false).ToArray();
  216. }
  217. /// <summary>Returns a drive name as a string.</summary>
  218. /// <returns>The name of the drive.</returns>
  219. /// <remarks>This method returns the Name property.</remarks>
  220. public override string ToString()
  221. {
  222. return _name;
  223. }
  224. /// <summary>[AlphaFS] Enumerates the drive names of all logical drives on a computer.</summary>
  225. /// <param name="fromEnvironment">Retrieve logical drives as known by the Environment.</param>
  226. /// <param name="isReady">Retrieve only when accessible (IsReady) logical drives.</param>
  227. /// <returns>
  228. /// An IEnumerable of type <see cref="Alphaleonis.Win32.Filesystem.DriveInfo"/> that represents
  229. /// the logical drives on a computer.
  230. /// </returns>
  231. [SecurityCritical]
  232. public static IEnumerable<DriveInfo> EnumerateDrives(bool fromEnvironment, bool isReady)
  233. {
  234. return Directory.EnumerateLogicalDrivesCore(fromEnvironment, isReady);
  235. }
  236. /// <summary>[AlphaFS] Gets the first available drive letter on the local system.</summary>
  237. /// <returns>A drive letter as <see cref="char"/>. When no drive letters are available, an exception is thrown.</returns>
  238. /// <remarks>The letters "A" and "B" are reserved for floppy drives and will never be returned by this function.</remarks>
  239. public static char GetFreeDriveLetter()
  240. {
  241. return GetFreeDriveLetter(false);
  242. }
  243. /// <summary>Gets an available drive letter on the local system.</summary>
  244. /// <param name="getLastAvailable">When <see langword="true"/> get the last available drive letter. When <see langword="false"/> gets the first available drive letter.</param>
  245. /// <returns>A drive letter as <see cref="char"/>. When no drive letters are available, an exception is thrown.</returns>
  246. /// <remarks>The letters "A" and "B" are reserved for floppy drives and will never be returned by this function.</remarks>
  247. [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
  248. [SuppressMessage("Microsoft.Usage", "CA2201:DoNotRaiseReservedExceptionTypes")]
  249. public static char GetFreeDriveLetter(bool getLastAvailable)
  250. {
  251. IEnumerable<char> freeDriveLetters = "CDEFGHIJKLMNOPQRSTUVWXYZ".Except(Directory.EnumerateLogicalDrivesCore(false, false).Select(d => d.Name[0]));
  252. try
  253. {
  254. return getLastAvailable ? freeDriveLetters.Last() : freeDriveLetters.First();
  255. }
  256. catch
  257. {
  258. throw new Exception("There are no drive letters available.");
  259. }
  260. }
  261. #endregion // Methods
  262. #region Private Methods
  263. /// <summary>Retrieves information about the file system and volume associated with the specified root file or directorystream.</summary>
  264. [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  265. [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  266. [SecurityCritical]
  267. private object GetDeviceInfo(int type, int mode)
  268. {
  269. try
  270. {
  271. switch (type)
  272. {
  273. #region Volume
  274. // VolumeInfo properties.
  275. case 0:
  276. if (Utils.IsNullOrWhiteSpace(_volumeInfo.FullPath))
  277. _volumeInfo.Refresh();
  278. switch (mode)
  279. {
  280. case 0:
  281. // IsVolume, VolumeInfo
  282. return _volumeInfo;
  283. case 1:
  284. // DriveFormat
  285. return _volumeInfo == null ? DriveType.Unknown.ToString() : _volumeInfo.FileSystemName ?? DriveType.Unknown.ToString();
  286. case 2:
  287. // VolumeLabel
  288. return _volumeInfo == null ? string.Empty : _volumeInfo.Name ?? string.Empty;
  289. }
  290. break;
  291. // Volume related.
  292. case 1:
  293. switch (mode)
  294. {
  295. case 0:
  296. // DosDeviceName
  297. // Do not use ?? expression here.
  298. if (_dosDeviceName == null)
  299. _dosDeviceName = Volume.QueryDosDevice(Name).FirstOrDefault();
  300. return _dosDeviceName;
  301. }
  302. break;
  303. #endregion // Volume
  304. #region Drive
  305. // Drive related.
  306. case 2:
  307. switch (mode)
  308. {
  309. case 0:
  310. // DriveType
  311. // Do not use ?? expression here.
  312. if (_driveType == null)
  313. _driveType = Volume.GetDriveType(Name);
  314. return _driveType;
  315. case 1:
  316. // RootDirectory
  317. // Do not use ?? expression here.
  318. if (_rootDirectory == null)
  319. _rootDirectory = new DirectoryInfo(null, Name, PathFormat.RelativePath);
  320. return _rootDirectory;
  321. }
  322. break;
  323. // DiskSpaceInfo related.
  324. case 3:
  325. switch (mode)
  326. {
  327. case 0:
  328. // AvailableFreeSpace, TotalFreeSpace, TotalSize, DiskSpaceInfo
  329. if (!_initDsie)
  330. {
  331. _dsi.Refresh();
  332. _initDsie = true;
  333. }
  334. break;
  335. }
  336. break;
  337. #endregion // Drive
  338. }
  339. }
  340. catch
  341. {
  342. }
  343. return type == 0 && mode > 0 ? string.Empty : null;
  344. }
  345. #endregion // Private
  346. }
  347. }