source: branches/eraser6/CodeReview/Eraser.Util/File.cs @ 1705

Revision 1705, 13.1 KB checked in by lowjoel, 4 years ago (diff)

Replace all Marshal.GetExceptionForHR with Win32ErrorCode.GetExceptionForWin32Error since we deal with Win32 errors not unlike COM errors, except with a few exceptions and that we should be throwing Win32Exception instead of COMException.

  • 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: Kasra Nassiri <cjax@users.sourceforge.net> @10/7/2008
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.Runtime.InteropServices;
27using System.ComponentModel;
28using System.Windows.Forms;
29using System.Drawing;
30using System.IO;
31using Microsoft.Win32.SafeHandles;
32using System.Globalization;
33
34namespace Eraser.Util
35{
36    public static class File
37    {
38        /// <summary>
39        /// Gets the list of ADSes of the given file.
40        /// </summary>
41        /// <param name="info">The FileInfo object with the file path etc.</param>
42        /// <returns>A list containing the names of the ADSes of each file. The
43        /// list will be empty if no ADSes exist.</returns>
44        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
45        public static IList<string> GetADSes(FileInfo info)
46        {
47            List<string> result = new List<string>();
48            using (FileStream stream = new StreamInfo(info.FullName).Open(FileMode.Open,
49                FileAccess.Read, FileShare.ReadWrite))
50            using (SafeFileHandle streamHandle = stream.SafeFileHandle)
51            {
52                //Allocate the structures
53                NativeMethods.FILE_STREAM_INFORMATION[] streams = GetADSes(streamHandle);
54
55                foreach (NativeMethods.FILE_STREAM_INFORMATION streamInfo in streams)
56                {
57                    //Get the name of the stream. The raw value is :NAME:$DATA
58                    string streamName = streamInfo.StreamName.Substring(1,
59                        streamInfo.StreamName.LastIndexOf(':') - 1);
60                   
61                    if (streamName.Length != 0)
62                        result.Add(streamName);
63                }
64            }
65
66            return result.AsReadOnly();
67        }
68
69        private static NativeMethods.FILE_STREAM_INFORMATION[] GetADSes(SafeFileHandle FileHandle)
70        {
71            NativeMethods.IO_STATUS_BLOCK status = new NativeMethods.IO_STATUS_BLOCK();
72            IntPtr fileInfoPtr = IntPtr.Zero;
73
74            try
75            {
76                NativeMethods.FILE_STREAM_INFORMATION streamInfo =
77                    new NativeMethods.FILE_STREAM_INFORMATION();
78                int fileInfoPtrLength = (Marshal.SizeOf(streamInfo) + 32768) / 2;
79                uint ntStatus = 0;
80
81                do
82                {
83                    fileInfoPtrLength *= 2;
84                    if (fileInfoPtr != IntPtr.Zero)
85                        Marshal.FreeHGlobal(fileInfoPtr);
86                    fileInfoPtr = Marshal.AllocHGlobal(fileInfoPtrLength);
87
88                    ntStatus = NativeMethods.NtQueryInformationFile(FileHandle, ref status,
89                        fileInfoPtr, (uint)fileInfoPtrLength,
90                        NativeMethods.FILE_INFORMATION_CLASS.FileStreamInformation);
91                }
92                while (ntStatus != 0 /*STATUS_SUCCESS*/ && ntStatus == 0x80000005 /*STATUS_BUFFER_OVERFLOW*/);
93
94                //Marshal the structure manually (argh!)
95                List<NativeMethods.FILE_STREAM_INFORMATION> result =
96                    new List<NativeMethods.FILE_STREAM_INFORMATION>();
97                unsafe
98                {
99                    for (byte* i = (byte*)fileInfoPtr; streamInfo.NextEntryOffset != 0;
100                        i += streamInfo.NextEntryOffset)
101                    {
102                        byte* currStreamPtr = i;
103                        streamInfo.NextEntryOffset = *(uint*)currStreamPtr;
104                        currStreamPtr += sizeof(uint);
105
106                        streamInfo.StreamNameLength = *(uint*)currStreamPtr;
107                        currStreamPtr += sizeof(uint);
108
109                        streamInfo.StreamSize = *(long*)currStreamPtr;
110                        currStreamPtr += sizeof(long);
111
112                        streamInfo.StreamAllocationSize = *(long*)currStreamPtr;
113                        currStreamPtr += sizeof(long);
114
115                        streamInfo.StreamName = Marshal.PtrToStringUni((IntPtr)currStreamPtr,
116                            (int)streamInfo.StreamNameLength / 2);
117                        result.Add(streamInfo);
118                    }
119                }
120
121                return result.ToArray();
122            }
123            finally
124            {
125                Marshal.FreeHGlobal(fileInfoPtr);
126            }
127        }
128
129        /// <summary>
130        /// Uses SHGetFileInfo to retrieve the description for the given file,
131        /// folder or drive.
132        /// </summary>
133        /// <param name="path">A string that contains the path and file name for
134        /// the file in question. Both absolute and relative paths are valid.
135        /// Directories and volumes must contain the trailing \</param>
136        /// <returns>A string containing the description</returns>
137        public static string GetFileDescription(string path)
138        {
139            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
140            NativeMethods.SHGetFileInfo(path, 0, ref shfi, Marshal.SizeOf(shfi),
141                NativeMethods.SHGetFileInfoFlags.SHGFI_DISPLAYNAME);
142            return shfi.szDisplayName;
143        }
144
145        /// <summary>
146        /// Uses SHGetFileInfo to retrieve the icon for the given file, folder or
147        /// drive.
148        /// </summary>
149        /// <param name="path">A string that contains the path and file name for
150        /// the file in question. Both absolute and relative paths are valid.
151        /// Directories and volumes must contain the trailing \</param>
152        /// <returns>An Icon object containing the bitmap</returns>
153        public static Icon GetFileIcon(string path)
154        {
155            NativeMethods.SHFILEINFO shfi = new NativeMethods.SHFILEINFO();
156            NativeMethods.SHGetFileInfo(path, 0, ref shfi, Marshal.SizeOf(shfi),
157                NativeMethods.SHGetFileInfoFlags.SHGFI_SMALLICON |
158                NativeMethods.SHGetFileInfoFlags.SHGFI_ICON);
159
160            if (shfi.hIcon != IntPtr.Zero)
161                return Icon.FromHandle(shfi.hIcon);
162            else
163                throw new IOException(string.Format(CultureInfo.CurrentCulture,
164                    "Could not load file icon from {0}", path),
165                    Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error()));
166        }
167
168        /// <summary>
169        /// Compacts the file path, fitting in the given width.
170        /// </summary>
171        /// <param name="longPath">The long file path.</param>
172        /// <param name="newWidth">The target width of the text.</param>
173        /// <param name="drawFont">The font used for drawing the text.</param>
174        /// <returns>The compacted file path.</returns>
175        public static string GetCompactPath(string longPath, int newWidth, Font drawFont)
176        {
177            using (Control ctrl = new Control())
178            {
179                //First check if the source string is too long.
180                Graphics g = ctrl.CreateGraphics();
181                int width = g.MeasureString(longPath, drawFont).ToSize().Width;
182                if (width <= newWidth)
183                    return longPath;
184
185                //It is, shorten it.
186                int aveCharWidth = width / longPath.Length;
187                int charCount = newWidth / aveCharWidth;
188                StringBuilder builder = new StringBuilder();
189                builder.Append(longPath);
190                builder.EnsureCapacity(charCount);
191
192                while (g.MeasureString(builder.ToString(), drawFont).Width > newWidth)
193                {
194                    if (!NativeMethods.PathCompactPathEx(builder, longPath,
195                        (uint)charCount--, 0))
196                    {
197                        return string.Empty;
198                    }
199                }
200
201                return builder.ToString();
202            }
203        }
204
205        /// <summary>
206        /// Determines if a given file is protected by SFC.
207        /// </summary>
208        /// <param name="filePath">The path to check</param>
209        /// <returns>True if the file is protected.</returns>
210        public static bool IsProtectedSystemFile(string filePath)
211        {
212            return NativeMethods.SfcIsFileProtected(IntPtr.Zero, filePath);
213        }
214
215        /// <summary>
216        /// Checks whether the path given is compressed.
217        /// </summary>
218        /// <param name="path">The path to the file or folder</param>
219        /// <returns>True if the file or folder is compressed.</returns>
220        public static bool IsCompressed(string path)
221        {
222            ushort compressionStatus = 0;
223            uint bytesReturned = 0;
224
225            using (SafeFileHandle handle = NativeMethods.CreateFile(path,
226                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
227                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
228                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
229            {
230                if (NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_GET_COMPRESSION,
231                    IntPtr.Zero, 0, out compressionStatus, sizeof(ushort), out bytesReturned,
232                    IntPtr.Zero))
233                {
234                    return compressionStatus != NativeMethods.COMPRESSION_FORMAT_NONE;
235                }
236            }
237
238            return false;
239        }
240
241        /// <summary>
242        /// Sets whether the file system object pointed to by path is compressed.
243        /// </summary>
244        /// <param name="path">The path to the file or folder.</param>
245        /// <returns>True if the file or folder has its compression value set.</returns>
246        public static bool SetCompression(string path, bool compressed)
247        {
248            ushort compressionStatus = compressed ?
249                NativeMethods.COMPRESSION_FORMAT_DEFAULT :
250                NativeMethods.COMPRESSION_FORMAT_NONE;
251            uint bytesReturned = 0;
252
253            using (SafeFileHandle handle = NativeMethods.CreateFile(path,
254                NativeMethods.GENERIC_READ | NativeMethods.GENERIC_WRITE,
255                0, IntPtr.Zero, NativeMethods.OPEN_EXISTING,
256                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
257            {
258                return NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_SET_COMPRESSION,
259                    ref compressionStatus, sizeof(ushort), IntPtr.Zero, 0, out bytesReturned,
260                    IntPtr.Zero);
261            }
262        }
263
264        /// <summary>
265        /// Gets the human-readable representation of a file size from the byte-wise
266        /// length of a file. This returns a KB = 1024 bytes (Windows convention.)
267        /// </summary>
268        /// <param name="bytes">The file size to scale.</param>
269        /// <returns>A string containing the file size and the associated unit.
270        /// Files larger than 1MB will be accurate to 2 decimal places.</returns>
271        public static string GetHumanReadableFileSize(long bytes)
272        {
273            //List of units, in ascending scale
274            string[] units = new string[] {
275                "bytes",
276                "KB",
277                "MB",
278                "GB",
279                "TB",
280                "PB",
281                "EB"
282            };
283
284            double dBytes = (double)bytes;
285            for (int i = 0; i != units.Length; ++i)
286            {
287                if (dBytes < 1000.0)
288                    if (i <= 1)
289                        return string.Format(CultureInfo.CurrentCulture,
290                            "{0} {1}", (int)dBytes, units[i]);
291                    else
292                        return string.Format(CultureInfo.CurrentCulture,
293                            "{0:0.00} {1}", dBytes, units[i]);
294                dBytes /= 1024.0;
295            }
296
297            return string.Format(CultureInfo.CurrentCulture, "{0, 2} {1}",
298                dBytes, units[units.Length - 1]);
299        }
300
301        private static DateTime FileTimeToDateTime(System.Runtime.InteropServices.ComTypes.FILETIME value)
302        {
303            long time = (long)((((ulong)value.dwHighDateTime) << sizeof(int) * 8) |
304                (uint)value.dwLowDateTime);
305            return DateTime.FromFileTime(time);
306        }
307
308        private static System.Runtime.InteropServices.ComTypes.FILETIME DateTimeToFileTime(DateTime value)
309        {
310            long time = value.ToFileTime();
311
312            System.Runtime.InteropServices.ComTypes.FILETIME result =
313                new System.Runtime.InteropServices.ComTypes.FILETIME();
314            result.dwLowDateTime = (int)(time & 0xFFFFFFFFL);
315            result.dwHighDateTime = (int)(time >> 32);
316
317            return result;
318        }
319
320        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
321        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#")]
322        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#")]
323        public static void GetFileTime(SafeFileHandle file, out DateTime creationTime,
324            out DateTime accessedTime, out DateTime modifiedTime)
325        {
326            System.Runtime.InteropServices.ComTypes.FILETIME accessedTimeNative =
327                new System.Runtime.InteropServices.ComTypes.FILETIME();
328            System.Runtime.InteropServices.ComTypes.FILETIME modifiedTimeNative =
329                new System.Runtime.InteropServices.ComTypes.FILETIME();
330            System.Runtime.InteropServices.ComTypes.FILETIME createdTimeNative =
331                new System.Runtime.InteropServices.ComTypes.FILETIME();
332
333            if (!NativeMethods.GetFileTime(file, out createdTimeNative, out accessedTimeNative,
334                out modifiedTimeNative))
335            {
336                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
337            }
338
339            creationTime = FileTimeToDateTime(createdTimeNative);
340            accessedTime = FileTimeToDateTime(accessedTimeNative);
341            modifiedTime = FileTimeToDateTime(modifiedTimeNative);
342        }
343
344        public static void SetFileTime(SafeFileHandle file, DateTime creationTime,
345            DateTime accessedTime, DateTime modifiedTime)
346        {
347            System.Runtime.InteropServices.ComTypes.FILETIME accessedTimeNative =
348                new System.Runtime.InteropServices.ComTypes.FILETIME();
349            System.Runtime.InteropServices.ComTypes.FILETIME modifiedTimeNative =
350                new System.Runtime.InteropServices.ComTypes.FILETIME();
351            System.Runtime.InteropServices.ComTypes.FILETIME createdTimeNative =
352                new System.Runtime.InteropServices.ComTypes.FILETIME();
353
354            if (!NativeMethods.GetFileTime(file, out createdTimeNative,
355                out accessedTimeNative, out modifiedTimeNative))
356            {
357                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
358            }
359
360            if (creationTime != DateTime.MinValue)
361                createdTimeNative = DateTimeToFileTime(creationTime);
362            if (accessedTime != DateTime.MinValue)
363                accessedTimeNative = DateTimeToFileTime(accessedTime);
364            if (modifiedTime != DateTime.MinValue)
365                modifiedTimeNative = DateTimeToFileTime(modifiedTime);
366
367            if (!NativeMethods.SetFileTime(file, ref createdTimeNative,
368                ref accessedTimeNative, ref modifiedTimeNative))
369            {
370                throw Win32ErrorCode.GetExceptionForWin32Error(Marshal.GetLastWin32Error());
371            }
372        }
373    }
374}
Note: See TracBrowser for help on using the repository browser.