source: trunk/eraser/Eraser.Util/ExtensionMethods/IO.cs @ 2194

Revision 2194, 18.1 KB checked in by lowjoel, 5 years ago (diff)

Change all internal OpenHandle? functions to private, since they should not be callable from outside the class.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1/*
2 * $Id$
3 * Copyright 2008-2010 The Eraser Project
4 * Original Author: Joel Low <lowjoel@users.sourceforge.net>
5 * Modified By:
6 *
7 * This file is part of Eraser.
8 *
9 * Eraser is free software: you can redistribute it and/or modify it under the
10 * terms of the GNU General Public License as published by the Free Software
11 * Foundation, either version 3 of the License, or (at your option) any later
12 * version.
13 *
14 * Eraser is distributed in the hope that it will be useful, but WITHOUT ANY
15 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
16 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 *
18 * A copy of the GNU General Public License can be found at
19 * <http://www.gnu.org/licenses/>.
20 */
21
22using System;
23using System.Collections.Generic;
24using System.Text;
25
26using System.IO;
27using System.Drawing;
28using System.Runtime.InteropServices;
29using Microsoft.Win32.SafeHandles;
30using System.Globalization;
31using System.Windows.Forms;
32
33namespace Eraser.Util.ExtensionMethods
34{
35    /// <summary>
36    /// Implements extension methods for IO-bound operations.
37    /// </summary>
38    public static class IO
39    {
40        /// <summary>
41        /// Copies the file times from the provided file.
42        /// </summary>
43        /// <param name="rhs">The file times to copy from.</param>
44        public static void CopyTimes(this FileSystemInfo lhs, FileSystemInfo rhs)
45        {
46            lhs.CreationTimeUtc = rhs.CreationTimeUtc;
47            lhs.LastAccessTimeUtc = rhs.LastAccessTimeUtc;
48            lhs.LastWriteTimeUtc = rhs.LastWriteTimeUtc;
49        }
50
51        /// <summary>
52        /// Deeply sets the file times associated with the current
53        /// <see cref="FileInfo"/> object.
54        /// </summary>
55        /// <param name="updateTime">The time the basic information was last set.</param>
56        /// <param name="createdTime">The time the file was created.</param>
57        /// <param name="lastModifiedTime">The time the file was last modified.</param>
58        /// <param name="lastAccessedTime">The time the file was last accessed.</param>
59        public static void SetTimes(this FileSystemInfo info, DateTime updateTime,
60            DateTime createdTime, DateTime lastModifiedTime, DateTime lastAccessedTime)
61        {
62            FileInfo file = info as FileInfo;
63            DirectoryInfo directory = info as DirectoryInfo;
64
65            if (file != null)
66                file.SetTimes(updateTime, createdTime, lastModifiedTime, lastAccessedTime);
67            else if (directory != null)
68                directory.SetTimes(updateTime, createdTime, lastModifiedTime, lastAccessedTime);
69            else
70                throw new NotImplementedException();
71        }
72
73        /// <summary>
74        /// Deeply sets the file times associated with the current
75        /// <see cref="FileInfo"/> object.
76        /// </summary>
77        /// <param name="updateTime">The time the basic information was last set.</param>
78        /// <param name="createdTime">The time the file was created.</param>
79        /// <param name="lastModifiedTime">The time the file was last modified.</param>
80        /// <param name="lastAccessedTime">The time the file was last accessed.</param>
81        public static void SetTimes(this FileInfo info, DateTime updateTime,
82            DateTime createdTime, DateTime lastModifiedTime, DateTime lastAccessedTime)
83        {
84            using (SafeFileHandle handle = NativeMethods.CreateFile(info.FullName,
85                NativeMethods.FILE_WRITE_ATTRIBUTES, 0, IntPtr.Zero,
86                NativeMethods.OPEN_EXISTING, 0, IntPtr.Zero))
87            {
88                SetTimes(handle, updateTime, createdTime, lastModifiedTime, lastAccessedTime);
89            }
90        }
91
92        /// <summary>
93        /// Deeply sets the file times associated with the current
94        /// <see cref="DirectoryInfo"/> object.
95        /// </summary>
96        /// <param name="updateTime">The time the basic information was last set.</param>
97        /// <param name="createdTime">The time the file was created.</param>
98        /// <param name="lastModifiedTime">The time the file was last modified.</param>
99        /// <param name="lastAccessedTime">The time the file was last accessed.</param>
100        public static void SetTimes(this DirectoryInfo info, DateTime updateTime,
101            DateTime createdTime, DateTime lastModifiedTime, DateTime lastAccessedTime)
102        {
103            using (SafeFileHandle handle = NativeMethods.CreateFile(info.FullName,
104                NativeMethods.FILE_WRITE_ATTRIBUTES, (uint)FileShare.ReadWrite, IntPtr.Zero,
105                NativeMethods.OPEN_EXISTING, NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
106            {
107                SetTimes(handle, updateTime, createdTime, lastModifiedTime, lastAccessedTime);
108            }
109        }
110
111        internal static void SetTimes(SafeFileHandle handle, DateTime updateTime,
112            DateTime createdTime, DateTime lastModifiedTime, DateTime lastAccessedTime)
113        {
114            NativeMethods.FILE_BASIC_INFORMATION fileInfo =
115                new NativeMethods.FILE_BASIC_INFORMATION();
116            fileInfo.ChangeTime = updateTime.ToFileTime();
117            fileInfo.CreationTime = createdTime.ToFileTime();
118            fileInfo.LastAccessTime = lastAccessedTime.ToFileTime();
119            fileInfo.LastWriteTime = lastModifiedTime.ToFileTime();
120
121            if (fileInfo.ChangeTime == 0)
122                throw new ArgumentOutOfRangeException("updateTime");
123            if (fileInfo.CreationTime == 0)
124                throw new ArgumentOutOfRangeException("createdTime");
125            if (fileInfo.LastAccessTime == 0)
126                throw new ArgumentOutOfRangeException("lastAccessedTime");
127            if (fileInfo.LastWriteTime == 0)
128                throw new ArgumentOutOfRangeException("lastModifiedTime");
129
130            IntPtr fileInfoPtr = Marshal.AllocHGlobal(Marshal.SizeOf(fileInfo));
131            try
132            {
133                Marshal.StructureToPtr(fileInfo, fileInfoPtr, true);
134                NativeMethods.IO_STATUS_BLOCK status;
135                uint result = NativeMethods.NtSetInformationFile(handle,
136                    out status, fileInfoPtr, (uint)Marshal.SizeOf(fileInfo),
137                    NativeMethods.FILE_INFORMATION_CLASS.FileBasicInformation);
138
139                if (result != 0)
140                    throw new IOException();
141            }
142            finally
143            {
144                Marshal.FreeHGlobal(fileInfoPtr);
145            }
146        }
147
148        /// <summary>
149        /// Gets the parent directory of the current <see cref="System.IO.FileSystemInfo"/>
150        /// object.
151        /// </summary>
152        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
153        /// to query its parent.</param>
154        /// <returns>The parent directory of the current
155        /// <see cref="System.IO.FileSystemInfo"/> object, or null if info is
156        /// aleady the root</returns>
157        public static DirectoryInfo GetParent(this FileSystemInfo info)
158        {
159            FileInfo file = info as FileInfo;
160            DirectoryInfo directory = info as DirectoryInfo;
161
162            if (file != null)
163                return file.Directory;
164            else if (directory != null)
165                return directory.Parent;
166            else
167                throw new ArgumentException("Unknown FileSystemInfo type.");
168        }
169
170        /// <summary>
171        /// Moves the provided <see cref="System.IO.FileSystemInfo"/> object
172        /// to the provided path.
173        /// </summary>
174        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
175        /// to move.</param>
176        /// <param name="path">The path to move the object to.</param>
177        public static void MoveTo(this FileSystemInfo info, string path)
178        {
179            FileInfo file = info as FileInfo;
180            DirectoryInfo directory = info as DirectoryInfo;
181
182            if (file != null)
183                file.MoveTo(path);
184            else if (directory != null)
185                directory.MoveTo(path);
186            else
187                throw new ArgumentException("Unknown FileSystemInfo type.");
188        }
189
190        /// <summary>
191        /// Compacts the file path, fitting in the given width.
192        /// </summary>
193        /// <param name="info">The <see cref="System.IO.FileSystemObject"/> that should
194        /// get a compact path.</param>
195        /// <param name="newWidth">The target width of the text.</param>
196        /// <param name="drawFont">The font used for drawing the text.</param>
197        /// <returns>The compacted file path.</returns>
198        public static string GetCompactPath(this FileSystemInfo info, int newWidth, Font drawFont)
199        {
200            using (Control ctrl = new Control())
201            {
202                //First check if the source string is too long.
203                Graphics g = ctrl.CreateGraphics();
204                string longPath = info.FullName;
205                int width = g.MeasureString(longPath, drawFont).ToSize().Width;
206                if (width <= newWidth)
207                    return longPath;
208
209                //It is, shorten it.
210                int aveCharWidth = width / longPath.Length;
211                int charCount = newWidth / aveCharWidth;
212                StringBuilder builder = new StringBuilder();
213                builder.Append(longPath);
214                builder.EnsureCapacity(charCount);
215
216                while (g.MeasureString(builder.ToString(), drawFont).Width > newWidth)
217                {
218                    if (!NativeMethods.PathCompactPathEx(builder, longPath,
219                        (uint)charCount--, 0))
220                    {
221                        return string.Empty;
222                    }
223                }
224
225                return builder.ToString();
226            }
227        }
228
229        /// <summary>
230        /// Checks whether the path given is compressed.
231        /// </summary>
232        /// <param name="info">The <see cref="System.IO.FileInfo"/> object</param>
233        /// <returns>True if the file or folder is compressed.</returns>
234        public static bool IsCompressed(this FileSystemInfo info)
235        {
236            ushort compressionStatus = 0;
237            uint bytesReturned = 0;
238
239            using (SafeFileHandle handle = NativeMethods.CreateFile(info.FullName,
240                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
241                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
242                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
243            {
244                if (NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_GET_COMPRESSION,
245                    IntPtr.Zero, 0, out compressionStatus, sizeof(ushort), out bytesReturned,
246                    IntPtr.Zero))
247                {
248                    return compressionStatus != NativeMethods.COMPRESSION_FORMAT_NONE;
249                }
250            }
251
252            return false;
253        }
254
255        /// <summary>
256        /// Compresses the given file.
257        /// </summary>
258        /// <param name="info">The File to compress.</param>
259        /// <returns>The success ofthe compression</returns>
260        public static bool Compress(this FileSystemInfo info)
261        {
262            return SetCompression(info.FullName, true);
263        }
264
265        /// <summary>
266        /// Uncompresses the given file.
267        /// </summary>
268        /// <param name="info">The File to uncompress.</param>
269        /// <returns>The success ofthe uncompression</returns>
270        public static bool Uncompress(this FileSystemInfo info)
271        {
272            return SetCompression(info.FullName, false);
273        }
274
275        /// <summary>
276        /// Sets whether the file system object pointed to by path is compressed.
277        /// </summary>
278        /// <param name="path">The path to the file or folder.</param>
279        /// <returns>True if the file or folder has its compression value set.</returns>
280        private static bool SetCompression(string path, bool compressed)
281        {
282            ushort compressionStatus = compressed ?
283                NativeMethods.COMPRESSION_FORMAT_DEFAULT :
284                NativeMethods.COMPRESSION_FORMAT_NONE;
285            uint bytesReturned = 0;
286
287            using (SafeFileHandle handle = NativeMethods.CreateFile(path,
288                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
289                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
290                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
291            {
292                return NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_SET_COMPRESSION,
293                    ref compressionStatus, sizeof(ushort), IntPtr.Zero, 0, out bytesReturned,
294                    IntPtr.Zero);
295            }
296        }
297
298        /// <summary>
299        /// Uses SHGetFileInfo to retrieve the description for the given file,
300        /// folder or drive.
301        /// </summary>
302        /// <param name="info">The file system object to query the description of.</param>
303        /// <returns>A string containing the description</returns>
304        public static string GetDescription(this FileSystemInfo info)
305        {
306            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
307            NativeMethods.SHGetFileInfo(info.FullName, 0, ref shfi, Marshal.SizeOf(shfi),
308                NativeMethods.SHGetFileInfoFlags.SHGFI_DISPLAYNAME);
309            return shfi.szDisplayName;
310        }
311
312        /// <summary>
313        /// Uses SHGetFileInfo to retrieve the icon for the given file, folder or
314        /// drive.
315        /// </summary>
316        /// <param name="info">The file system object to query the description of.</param>
317        /// <returns>An Icon object containing the bitmap</returns>
318        public static Icon GetIcon(this FileSystemInfo info)
319        {
320            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
321            NativeMethods.SHGetFileInfo(info.FullName, 0, ref shfi, Marshal.SizeOf(shfi),
322                NativeMethods.SHGetFileInfoFlags.SHGFI_SMALLICON |
323                NativeMethods.SHGetFileInfoFlags.SHGFI_ICON);
324
325            if (shfi.hIcon != IntPtr.Zero)
326                return Icon.FromHandle(shfi.hIcon);
327            else
328                throw new IOException(string.Format(CultureInfo.CurrentCulture,
329                    S._("Could not load file icon from {0}"), info.FullName),
330                    Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
331        }
332
333        /// <summary>
334        /// Determines if a given file is protected by SFC.
335        /// </summary>
336        /// <param name="info">The file systme object to check.</param>
337        /// <returns>True if the file is protected.</returns>
338        public static bool IsProtectedSystemFile(this FileSystemInfo info)
339        {
340            return NativeMethods.SfcIsFileProtected(IntPtr.Zero, info.FullName);
341        }
342
343        /// <summary>
344        /// Copies an existing file to a new file, allowing the monitoring of the progress
345        /// of the copy operation.
346        /// </summary>
347        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
348        /// to copy.</param>
349        /// <param name="destFileName">The name of the new file to copy to.</param>
350        /// <param name="progress">The progress callback function to execute</param>
351        /// <returns>A new file, or an overwrite of an existing file if the file exists.</returns>
352        public static FileInfo CopyTo(this FileInfo info, string destFileName,
353            CopyProgressFunction progress)
354        {
355            bool cancel = false;
356            NativeMethods.CopyProgressFunction callback = delegate(
357                    long TotalFileSize, long TotalBytesTransferred, long StreamSize,
358                    long StreamBytesTransferred, uint dwStreamNumber,
359                    NativeMethods.CopyProgressFunctionCallbackReasons dwCallbackReason,
360                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
361            {
362                return progress(TotalFileSize, TotalBytesTransferred);
363            };
364
365            if (!NativeMethods.CopyFileEx(info.FullName, destFileName, callback, IntPtr.Zero,
366                ref cancel, 0))
367            {
368                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
369            }
370
371            return new FileInfo(destFileName);
372        }
373
374        /// <summary>
375        /// An application-defined callback function used with the <see cref="CopyTo" />
376        /// function. It is called when a portion of a copy or move operation is
377        /// completed.
378        /// </summary>
379        /// <param name="TotalFileSize">The total size of the file, in bytes.</param>
380        /// <param name="TotalBytesTransferred">The total number of bytes
381        /// transferred from the source file to the destination file since the
382        /// copy operation began.</param>
383        /// <returns>The <see cref="CopyProgressFunction"/> function should return
384        /// one of the <see cref="CopyProgressFunctionResult"/> values.</returns>
385        public delegate CopyProgressFunctionResult CopyProgressFunction(
386            long TotalFileSize, long TotalBytesTransferred);
387
388        /// <summary>
389        /// Result codes which can be returned from the
390        /// <see cref="CopyProgressFunction"/> callbacks.
391        /// </summary>
392        public enum CopyProgressFunctionResult
393        {
394            /// <summary>
395            /// Cancel the copy operation and delete the destination file.
396            /// </summary>
397            Cancel = 1,
398
399            /// <summary>
400            /// Continue the copy operation.
401            /// </summary>
402            Continue = 0,
403
404            /// <summary>
405            /// Continue the copy operation, but stop invoking
406            /// <see cref="CopyProgressRoutine"/> to report progress.
407            /// </summary>
408            Quiet = 3,
409
410            /// <summary>
411            /// Stop the copy operation. It can be restarted at a later time.
412            /// </summary>
413            Stop = 2
414        }
415
416        /// <summary>
417        /// Gets the list of ADSes of the given file.
418        /// </summary>
419        /// <param name="info">The FileInfo object with the file path etc.</param>
420        /// <returns>A list containing the names of the ADSes of each file. The
421        /// list will be empty if no ADSes exist.</returns>
422        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
423        public static IList<StreamInfo> GetADSes(this FileInfo info)
424        {
425            List<StreamInfo> result = new List<StreamInfo>();
426            using (SafeFileHandle streamHandle = NativeMethods.CreateFile(info.FullName,
427                NativeMethods.GENERIC_READ, (uint)FileShare.ReadWrite, IntPtr.Zero,
428                (uint)FileMode.Open, (uint)FileOptions.None, IntPtr.Zero))
429            {
430                if (streamHandle.IsInvalid)
431                    throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
432
433                //Allocate the structures
434                NativeMethods.FILE_STREAM_INFORMATION[] streams = GetADSes(streamHandle);
435
436                foreach (NativeMethods.FILE_STREAM_INFORMATION streamInfo in streams)
437                {
438                    //Get the name of the stream. The raw value is :NAME:$DATA
439                    string streamName = streamInfo.StreamName.Substring(1,
440                        streamInfo.StreamName.LastIndexOf(':') - 1);
441
442                    if (streamName.Length != 0)
443                        result.Add(new StreamInfo(info.FullName, streamName));
444                }
445            }
446
447            return result.AsReadOnly();
448        }
449
450        private static NativeMethods.FILE_STREAM_INFORMATION[] GetADSes(SafeFileHandle FileHandle)
451        {
452            NativeMethods.IO_STATUS_BLOCK status = new NativeMethods.IO_STATUS_BLOCK();
453            IntPtr fileInfoPtr = IntPtr.Zero;
454
455            try
456            {
457                NativeMethods.FILE_STREAM_INFORMATION streamInfo =
458                    new NativeMethods.FILE_STREAM_INFORMATION();
459                int fileInfoPtrLength = (Marshal.SizeOf(streamInfo) + 32768) / 2;
460                uint ntStatus = 0;
461
462                do
463                {
464                    fileInfoPtrLength *= 2;
465                    if (fileInfoPtr != IntPtr.Zero)
466                        Marshal.FreeHGlobal(fileInfoPtr);
467                    fileInfoPtr = Marshal.AllocHGlobal(fileInfoPtrLength);
468
469                    ntStatus = NativeMethods.NtQueryInformationFile(FileHandle, ref status,
470                        fileInfoPtr, (uint)fileInfoPtrLength,
471                        NativeMethods.FILE_INFORMATION_CLASS.FileStreamInformation);
472                }
473                while (ntStatus != 0 /*STATUS_SUCCESS*/ && ntStatus == 0x80000005 /*STATUS_BUFFER_OVERFLOW*/);
474
475                //Marshal the structure manually (argh!)
476                List<NativeMethods.FILE_STREAM_INFORMATION> result =
477                    new List<NativeMethods.FILE_STREAM_INFORMATION>();
478                unsafe
479                {
480                    for (byte* i = (byte*)fileInfoPtr; streamInfo.NextEntryOffset != 0;
481                        i += streamInfo.NextEntryOffset)
482                    {
483                        byte* currStreamPtr = i;
484                        streamInfo.NextEntryOffset = *(uint*)currStreamPtr;
485                        currStreamPtr += sizeof(uint);
486
487                        streamInfo.StreamNameLength = *(uint*)currStreamPtr;
488                        currStreamPtr += sizeof(uint);
489
490                        streamInfo.StreamSize = *(long*)currStreamPtr;
491                        currStreamPtr += sizeof(long);
492
493                        streamInfo.StreamAllocationSize = *(long*)currStreamPtr;
494                        currStreamPtr += sizeof(long);
495
496                        streamInfo.StreamName = Marshal.PtrToStringUni((IntPtr)currStreamPtr,
497                            (int)streamInfo.StreamNameLength / 2);
498                        result.Add(streamInfo);
499                    }
500                }
501
502                return result.ToArray();
503            }
504            finally
505            {
506                Marshal.FreeHGlobal(fileInfoPtr);
507            }
508        }
509    }
510}
Note: See TracBrowser for help on using the repository browser.