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

Revision 2151, 13.9 KB checked in by lowjoel, 5 years ago (diff)

Reorganise the extension methods by the class it extends.

  • 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        /// Gets the parent directory of the current <see cref="System.IO.FileSystemInfo"/>
53        /// object.
54        /// </summary>
55        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
56        /// to query its parent.</param>
57        /// <returns>The parent directory of the current
58        /// <see cref="System.IO.FileSystemInfo"/> object, or null if info is
59        /// aleady the root</returns>
60        public static DirectoryInfo GetParent(this FileSystemInfo info)
61        {
62            FileInfo file = info as FileInfo;
63            DirectoryInfo directory = info as DirectoryInfo;
64
65            if (file != null)
66                return file.Directory;
67            else if (directory != null)
68                return directory.Parent;
69            else
70                throw new ArgumentException("Unknown FileSystemInfo type.");
71        }
72
73        /// <summary>
74        /// Moves the provided <see cref="System.IO.FileSystemInfo"/> object
75        /// to the provided path.
76        /// </summary>
77        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
78        /// to move.</param>
79        /// <param name="path">The path to move the object to.</param>
80        public static void MoveTo(this FileSystemInfo info, string path)
81        {
82            FileInfo file = info as FileInfo;
83            DirectoryInfo directory = info as DirectoryInfo;
84
85            if (file != null)
86                file.MoveTo(path);
87            else if (directory != null)
88                directory.MoveTo(path);
89            else
90                throw new ArgumentException("Unknown FileSystemInfo type.");
91        }
92
93        /// <summary>
94        /// Compacts the file path, fitting in the given width.
95        /// </summary>
96        /// <param name="info">The <see cref="System.IO.FileSystemObject"/> that should
97        /// get a compact path.</param>
98        /// <param name="newWidth">The target width of the text.</param>
99        /// <param name="drawFont">The font used for drawing the text.</param>
100        /// <returns>The compacted file path.</returns>
101        public static string GetCompactPath(this FileSystemInfo info, int newWidth, Font drawFont)
102        {
103            using (Control ctrl = new Control())
104            {
105                //First check if the source string is too long.
106                Graphics g = ctrl.CreateGraphics();
107                string longPath = info.FullName;
108                int width = g.MeasureString(longPath, drawFont).ToSize().Width;
109                if (width <= newWidth)
110                    return longPath;
111
112                //It is, shorten it.
113                int aveCharWidth = width / longPath.Length;
114                int charCount = newWidth / aveCharWidth;
115                StringBuilder builder = new StringBuilder();
116                builder.Append(longPath);
117                builder.EnsureCapacity(charCount);
118
119                while (g.MeasureString(builder.ToString(), drawFont).Width > newWidth)
120                {
121                    if (!NativeMethods.PathCompactPathEx(builder, longPath,
122                        (uint)charCount--, 0))
123                    {
124                        return string.Empty;
125                    }
126                }
127
128                return builder.ToString();
129            }
130        }
131
132        /// <summary>
133        /// Checks whether the path given is compressed.
134        /// </summary>
135        /// <param name="info">The <see cref="System.IO.FileInfo"/> object</param>
136        /// <returns>True if the file or folder is compressed.</returns>
137        public static bool IsCompressed(this FileSystemInfo info)
138        {
139            ushort compressionStatus = 0;
140            uint bytesReturned = 0;
141
142            using (SafeFileHandle handle = NativeMethods.CreateFile(info.FullName,
143                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
144                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
145                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
146            {
147                if (NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_GET_COMPRESSION,
148                    IntPtr.Zero, 0, out compressionStatus, sizeof(ushort), out bytesReturned,
149                    IntPtr.Zero))
150                {
151                    return compressionStatus != NativeMethods.COMPRESSION_FORMAT_NONE;
152                }
153            }
154
155            return false;
156        }
157
158        /// <summary>
159        /// Compresses the given file.
160        /// </summary>
161        /// <param name="info">The File to compress.</param>
162        /// <returns>The success ofthe compression</returns>
163        public static bool Compress(this FileSystemInfo info)
164        {
165            return SetCompression(info.FullName, true);
166        }
167
168        /// <summary>
169        /// Uncompresses the given file.
170        /// </summary>
171        /// <param name="info">The File to uncompress.</param>
172        /// <returns>The success ofthe uncompression</returns>
173        public static bool Uncompress(this FileSystemInfo info)
174        {
175            return SetCompression(info.FullName, false);
176        }
177
178        /// <summary>
179        /// Sets whether the file system object pointed to by path is compressed.
180        /// </summary>
181        /// <param name="path">The path to the file or folder.</param>
182        /// <returns>True if the file or folder has its compression value set.</returns>
183        private static bool SetCompression(string path, bool compressed)
184        {
185            ushort compressionStatus = compressed ?
186                NativeMethods.COMPRESSION_FORMAT_DEFAULT :
187                NativeMethods.COMPRESSION_FORMAT_NONE;
188            uint bytesReturned = 0;
189
190            using (SafeFileHandle handle = NativeMethods.CreateFile(path,
191                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
192                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
193                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
194            {
195                return NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_SET_COMPRESSION,
196                    ref compressionStatus, sizeof(ushort), IntPtr.Zero, 0, out bytesReturned,
197                    IntPtr.Zero);
198            }
199        }
200
201        /// <summary>
202        /// Uses SHGetFileInfo to retrieve the description for the given file,
203        /// folder or drive.
204        /// </summary>
205        /// <param name="info">The file system object to query the description of.</param>
206        /// <returns>A string containing the description</returns>
207        public static string GetDescription(this FileSystemInfo info)
208        {
209            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
210            NativeMethods.SHGetFileInfo(info.FullName, 0, ref shfi, Marshal.SizeOf(shfi),
211                NativeMethods.SHGetFileInfoFlags.SHGFI_DISPLAYNAME);
212            return shfi.szDisplayName;
213        }
214
215        /// <summary>
216        /// Uses SHGetFileInfo to retrieve the icon for the given file, folder or
217        /// drive.
218        /// </summary>
219        /// <param name="info">The file system object to query the description of.</param>
220        /// <returns>An Icon object containing the bitmap</returns>
221        public static Icon GetIcon(this FileSystemInfo info)
222        {
223            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
224            NativeMethods.SHGetFileInfo(info.FullName, 0, ref shfi, Marshal.SizeOf(shfi),
225                NativeMethods.SHGetFileInfoFlags.SHGFI_SMALLICON |
226                NativeMethods.SHGetFileInfoFlags.SHGFI_ICON);
227
228            if (shfi.hIcon != IntPtr.Zero)
229                return Icon.FromHandle(shfi.hIcon);
230            else
231                throw new IOException(string.Format(CultureInfo.CurrentCulture,
232                    S._("Could not load file icon from {0}"), info.FullName),
233                    Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
234        }
235
236        /// <summary>
237        /// Determines if a given file is protected by SFC.
238        /// </summary>
239        /// <param name="info">The file systme object to check.</param>
240        /// <returns>True if the file is protected.</returns>
241        public static bool IsProtectedSystemFile(this FileSystemInfo info)
242        {
243            return NativeMethods.SfcIsFileProtected(IntPtr.Zero, info.FullName);
244        }
245
246        /// <summary>
247        /// Copies an existing file to a new file, allowing the monitoring of the progress
248        /// of the copy operation.
249        /// </summary>
250        /// <param name="info">The <see cref="System.IO.FileSystemInfo"/> object
251        /// to copy.</param>
252        /// <param name="destFileName">The name of the new file to copy to.</param>
253        /// <param name="progress">The progress callback function to execute</param>
254        /// <returns>A new file, or an overwrite of an existing file if the file exists.</returns>
255        public static FileInfo CopyTo(this FileInfo info, string destFileName,
256            CopyProgressFunction progress)
257        {
258            bool cancel = false;
259            NativeMethods.CopyProgressFunction callback = delegate(
260                    long TotalFileSize, long TotalBytesTransferred, long StreamSize,
261                    long StreamBytesTransferred, uint dwStreamNumber,
262                    NativeMethods.CopyProgressFunctionCallbackReasons dwCallbackReason,
263                    IntPtr hSourceFile, IntPtr hDestinationFile, IntPtr lpData)
264            {
265                return progress(TotalFileSize, TotalBytesTransferred);
266            };
267
268            if (!NativeMethods.CopyFileEx(info.FullName, destFileName, callback, IntPtr.Zero,
269                ref cancel, 0))
270            {
271                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
272            }
273
274            return new FileInfo(destFileName);
275        }
276
277        /// <summary>
278        /// An application-defined callback function used with the <see cref="CopyTo" />
279        /// function. It is called when a portion of a copy or move operation is
280        /// completed.
281        /// </summary>
282        /// <param name="TotalFileSize">The total size of the file, in bytes.</param>
283        /// <param name="TotalBytesTransferred">The total number of bytes
284        /// transferred from the source file to the destination file since the
285        /// copy operation began.</param>
286        /// <returns>The <see cref="CopyProgressFunction"/> function should return
287        /// one of the <see cref="CopyProgressFunctionResult"/> values.</returns>
288        public delegate CopyProgressFunctionResult CopyProgressFunction(
289            long TotalFileSize, long TotalBytesTransferred);
290
291        /// <summary>
292        /// Result codes which can be returned from the
293        /// <see cref="CopyProgressFunction"/> callbacks.
294        /// </summary>
295        public enum CopyProgressFunctionResult
296        {
297            /// <summary>
298            /// Cancel the copy operation and delete the destination file.
299            /// </summary>
300            Cancel = 1,
301
302            /// <summary>
303            /// Continue the copy operation.
304            /// </summary>
305            Continue = 0,
306
307            /// <summary>
308            /// Continue the copy operation, but stop invoking
309            /// <see cref="CopyProgressRoutine"/> to report progress.
310            /// </summary>
311            Quiet = 3,
312
313            /// <summary>
314            /// Stop the copy operation. It can be restarted at a later time.
315            /// </summary>
316            Stop = 2
317        }
318
319        /// <summary>
320        /// Gets the list of ADSes of the given file.
321        /// </summary>
322        /// <param name="info">The FileInfo object with the file path etc.</param>
323        /// <returns>A list containing the names of the ADSes of each file. The
324        /// list will be empty if no ADSes exist.</returns>
325        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
326        public static IList<StreamInfo> GetADSes(this FileInfo info)
327        {
328            List<StreamInfo> result = new List<StreamInfo>();
329            using (FileStream stream = new StreamInfo(info.FullName).Open(FileMode.Open,
330                FileAccess.Read, FileShare.ReadWrite))
331            using (SafeFileHandle streamHandle = stream.SafeFileHandle)
332            {
333                //Allocate the structures
334                NativeMethods.FILE_STREAM_INFORMATION[] streams = GetADSes(streamHandle);
335
336                foreach (NativeMethods.FILE_STREAM_INFORMATION streamInfo in streams)
337                {
338                    //Get the name of the stream. The raw value is :NAME:$DATA
339                    string streamName = streamInfo.StreamName.Substring(1,
340                        streamInfo.StreamName.LastIndexOf(':') - 1);
341
342                    if (streamName.Length != 0)
343                        result.Add(new StreamInfo(info.FullName, streamName));
344                }
345            }
346
347            return result.AsReadOnly();
348        }
349
350        private static NativeMethods.FILE_STREAM_INFORMATION[] GetADSes(SafeFileHandle FileHandle)
351        {
352            NativeMethods.IO_STATUS_BLOCK status = new NativeMethods.IO_STATUS_BLOCK();
353            IntPtr fileInfoPtr = IntPtr.Zero;
354
355            try
356            {
357                NativeMethods.FILE_STREAM_INFORMATION streamInfo =
358                    new NativeMethods.FILE_STREAM_INFORMATION();
359                int fileInfoPtrLength = (Marshal.SizeOf(streamInfo) + 32768) / 2;
360                uint ntStatus = 0;
361
362                do
363                {
364                    fileInfoPtrLength *= 2;
365                    if (fileInfoPtr != IntPtr.Zero)
366                        Marshal.FreeHGlobal(fileInfoPtr);
367                    fileInfoPtr = Marshal.AllocHGlobal(fileInfoPtrLength);
368
369                    ntStatus = NativeMethods.NtQueryInformationFile(FileHandle, ref status,
370                        fileInfoPtr, (uint)fileInfoPtrLength,
371                        NativeMethods.FILE_INFORMATION_CLASS.FileStreamInformation);
372                }
373                while (ntStatus != 0 /*STATUS_SUCCESS*/ && ntStatus == 0x80000005 /*STATUS_BUFFER_OVERFLOW*/);
374
375                //Marshal the structure manually (argh!)
376                List<NativeMethods.FILE_STREAM_INFORMATION> result =
377                    new List<NativeMethods.FILE_STREAM_INFORMATION>();
378                unsafe
379                {
380                    for (byte* i = (byte*)fileInfoPtr; streamInfo.NextEntryOffset != 0;
381                        i += streamInfo.NextEntryOffset)
382                    {
383                        byte* currStreamPtr = i;
384                        streamInfo.NextEntryOffset = *(uint*)currStreamPtr;
385                        currStreamPtr += sizeof(uint);
386
387                        streamInfo.StreamNameLength = *(uint*)currStreamPtr;
388                        currStreamPtr += sizeof(uint);
389
390                        streamInfo.StreamSize = *(long*)currStreamPtr;
391                        currStreamPtr += sizeof(long);
392
393                        streamInfo.StreamAllocationSize = *(long*)currStreamPtr;
394                        currStreamPtr += sizeof(long);
395
396                        streamInfo.StreamName = Marshal.PtrToStringUni((IntPtr)currStreamPtr,
397                            (int)streamInfo.StreamNameLength / 2);
398                        result.Add(streamInfo);
399                    }
400                }
401
402                return result.ToArray();
403            }
404            finally
405            {
406                Marshal.FreeHGlobal(fileInfoPtr);
407            }
408        }
409    }
410}
Note: See TracBrowser for help on using the repository browser.