source: trunk/eraser/Eraser.Util/ExtensionMethods/PathUtil.cs @ 2960

Revision 2958, 10.6 KB checked in by gtrant, 2 months ago (diff)

Updated Copyright to 2014
Updated versions to 6.2
Fix for date stamp on erased artefacts

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Rev URL
Line 
1/*
2 * $Id$
3 * Copyright 2008-2014 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.Linq;
25using System.Text;
26using System.IO;
27using System.Drawing;
28using System.Windows.Forms;
29using System.Runtime.InteropServices;
30using Microsoft.Win32.SafeHandles;
31
32namespace Eraser.Util.ExtensionMethods
33{
34    /// <summary>
35    /// Additional Path utility methods.
36    /// </summary>
37    public class PathUtil
38    {
39        /// <summary>
40        /// Makes the first path relative to the second.
41        /// </summary>
42        /// <remarks>Modified from:
43        /// http://mrpmorris.blogspot.com/2007/05/convert-absolute-path-to-relative-path.html</remarks>
44        /// <param name="absolutePath">The path to use as the root of the relative path.</param>
45        /// <param name="relativeTo">The path to make relative.</param>
46        /// <returns>The relative path to the provided path.</returns>
47        public static string MakeRelativeTo(FileSystemInfo absolutePath, string relativeTo)
48        {
49            string[] absoluteDirectories = absolutePath.FullName.Split(
50                System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
51            string[] relativeDirectories = relativeTo.Split(
52                System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
53
54            //Get the shortest of the two paths
55            int length = absoluteDirectories.Length < relativeDirectories.Length ?
56                absoluteDirectories.Length : relativeDirectories.Length;
57
58            //Use to determine where in the loop we exited
59            int lastCommonRoot = -1;
60            int index;
61
62            //Find common root
63            for (index = 0; index < length; index++)
64                if (absoluteDirectories[index] == relativeDirectories[index])
65                    lastCommonRoot = index;
66                else
67                    break;
68
69            //If we didn't find a common prefix then throw
70            if (lastCommonRoot == -1)
71                throw new ArgumentException("Paths do not have a common base");
72
73            //Build up the relative path
74            StringBuilder relativePath = new StringBuilder();
75
76            //Add on the ..
77            for (index = lastCommonRoot + 1; index < absoluteDirectories.Length; index++)
78                if (absoluteDirectories[index].Length > 0)
79                    relativePath.Append("..\\");
80
81            //Add on the folders
82            for (index = lastCommonRoot + 1; index < relativeDirectories.Length - 1; index++)
83                relativePath.Append(relativeDirectories[index] + "\\");
84            if (lastCommonRoot < relativeDirectories.Length - 1)
85                relativePath.Append(relativeDirectories[relativeDirectories.Length - 1]);
86
87            return relativePath.ToString();
88        }
89
90        /// <summary>
91        /// Verifies if the path given is rooted at the given absolute path.
92        /// </summary>
93        /// <param name="absolutePath">The root path.</param>
94        /// <param name="path">The path to verify.</param>
95        /// <returns>True if the path provided is a subfolder/sub-file of the provided root path.</returns>
96        public static bool IsRootedAt(FileSystemInfo absolutePath, string path)
97        {
98            //Convert the path in question to an absolute path
99            if (!System.IO.Path.IsPathRooted(path))
100                path = System.IO.Path.GetFullPath(path);
101
102            //Split the directory path to its component folders
103            string[] absoluteDirectories = absolutePath.FullName.Split(
104                System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
105            string[] relativeDirectories = path.Split(
106                System.IO.Path.DirectorySeparatorChar, System.IO.Path.AltDirectorySeparatorChar);
107
108            //Compare element by element; if the absolute path compares till the end, the
109            //provided path is a subdirectory
110            for (int i = 0; i < absoluteDirectories.Length; ++i)
111                if (absoluteDirectories[i] != relativeDirectories[i])
112                    return false;
113
114            return true;
115        }
116
117        /// <summary>
118        /// Compacts the file path, fitting in the given width.
119        /// </summary>
120        /// <param name="longPath">The path to compact.</param>
121        /// <param name="newWidth">The target width of the text.</param>
122        /// <param name="drawFont">The font used for drawing the text.</param>
123        /// <returns>The compacted file path.</returns>
124        public static string GetCompactPath(string longPath, int newWidth, Font drawFont)
125        {
126            using (Control ctrl = new Control())
127            using (Graphics g = ctrl.CreateGraphics())
128            {
129                //First check if the source string is too long.
130                int width = g.MeasureString(longPath, drawFont).ToSize().Width;
131                if (width <= newWidth)
132                    return longPath;
133
134                //It is, shorten it.
135                int aveCharWidth = width / longPath.Length;
136                int charCount = newWidth / aveCharWidth;
137                StringBuilder builder = new StringBuilder();
138                builder.Append(longPath);
139                builder.EnsureCapacity(charCount);
140
141                while (g.MeasureString(builder.ToString(), drawFont).Width > newWidth)
142                {
143                    if (!NativeMethods.PathCompactPathEx(builder, longPath,
144                        (uint)charCount--, 0))
145                    {
146                        return string.Empty;
147                    }
148                }
149
150                return builder.ToString();
151            }
152        }
153
154        /// <summary>
155        /// Compacts the file path, fitting in the given width.
156        /// </summary>
157        /// <param name="longPath">The path to compact.</param>
158        /// <param name="newWidth">The target width of the text.</param>
159        /// <param name="control">The control on which this text is drawn. This is used
160        /// for font information.</param>
161        /// <returns>The compacted file path.</returns>
162        public static string GetCompactPath(string longPath, int newWidth, Control control)
163        {
164            return GetCompactPath(longPath, newWidth, control.Font);
165        }
166
167        /// <summary>
168        /// Resolves the reparse point pointed to by the path.
169        /// </summary>
170        /// <param name="path">The path to the reparse point.</param>
171        /// <returns>The NT Namespace Name of the reparse point.</returns>
172        public static string ResolveReparsePoint(string path)
173        {
174            if (string.IsNullOrEmpty(path))
175                throw new ArgumentException(path);
176
177            //If the path is a directory, remove the trailing \
178            if (Directory.Exists(path) && path[path.Length - 1] == '\\')
179                path = path.Remove(path.Length - 1);
180
181            using (SafeFileHandle handle = NativeMethods.CreateFile(path,
182                NativeMethods.FILE_READ_ATTRIBUTES | NativeMethods.FILE_READ_EA,
183                NativeMethods.FILE_SHARE_READ | NativeMethods.FILE_SHARE_WRITE,
184                IntPtr.Zero, NativeMethods.OPEN_EXISTING,
185                NativeMethods.FILE_FLAG_OPEN_REPARSE_POINT |
186                NativeMethods.FILE_FLAG_BACKUP_SEMANTICS, IntPtr.Zero))
187            {
188                if (handle.IsInvalid)
189                    throw new System.IO.IOException(string.Format("Cannot open handle to {0}", path));
190
191                int bufferSize = Marshal.SizeOf(typeof(NativeMethods.REPARSE_DATA_BUFFER)) + 260 * sizeof(char);
192                IntPtr buffer = Marshal.AllocHGlobal(bufferSize);
193
194                //Get all the information about the reparse point.
195                try
196                {
197                    uint returnedBytes = 0;
198                    while (!NativeMethods.DeviceIoControl(handle, NativeMethods.FSCTL_GET_REPARSE_POINT,
199                        IntPtr.Zero, 0, buffer, (uint)bufferSize, out returnedBytes, IntPtr.Zero))
200                    {
201                        if (Marshal.GetLastWin32Error() == 122) //ERROR_INSUFFICIENT_BUFFER
202                        {
203                            bufferSize *= 2;
204                            Marshal.FreeHGlobal(buffer);
205                            buffer = Marshal.AllocHGlobal(bufferSize);
206                        }
207                        else
208                        {
209                            throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
210                        }
211                    }
212
213                    string result = ResolveReparsePoint(buffer, path);
214
215                    //Is it a directory? If it is, we need to add a trailing \
216                    if (Directory.Exists(path) && !result.EndsWith("\\"))
217                        result += '\\';
218                    return result;
219                }
220                finally
221                {
222                    Marshal.FreeHGlobal(buffer);
223                }
224            }
225        }
226
227        private static string ResolveReparsePoint(IntPtr ptr, string path)
228        {
229            NativeMethods.REPARSE_DATA_BUFFER buffer = (NativeMethods.REPARSE_DATA_BUFFER)
230                Marshal.PtrToStructure(ptr, typeof(NativeMethods.REPARSE_DATA_BUFFER));
231
232            //Check that this Reparse Point has a Microsoft Tag
233            if (((uint)buffer.ReparseTag & (1 << 31)) == 0)
234            {
235                //We can only handle Microsoft's reparse tags.
236                throw new ArgumentException("Unknown Reparse point type.");
237            }
238
239            //Then handle the tags
240            switch (buffer.ReparseTag)
241            {
242                case NativeMethods.REPARSE_DATA_TAG.IO_REPARSE_TAG_MOUNT_POINT:
243                    return ResolveReparsePointJunction((IntPtr)(ptr.ToInt64() + Marshal.SizeOf(buffer)));
244
245                case NativeMethods.REPARSE_DATA_TAG.IO_REPARSE_TAG_SYMLINK:
246                    return ResolveReparsePointSymlink((IntPtr)(ptr.ToInt64() + Marshal.SizeOf(buffer)),
247                        path);
248
249                default:
250                    throw new ArgumentException("Unsupported Reparse point type.");
251            }
252        }
253
254        private static string ResolveReparsePointJunction(IntPtr ptr)
255        {
256            NativeMethods.REPARSE_DATA_BUFFER.MountPointReparseBuffer buffer =
257                (NativeMethods.REPARSE_DATA_BUFFER.MountPointReparseBuffer)
258                Marshal.PtrToStructure(ptr,
259                    typeof(NativeMethods.REPARSE_DATA_BUFFER.MountPointReparseBuffer));
260
261            //Get the substitute and print names from the buffer.
262            string substituteName;
263            string printName;
264            unsafe
265            {
266                char* path = (char*)(((byte*)ptr.ToInt64()) + Marshal.SizeOf(buffer));
267                printName = new string(path + (buffer.PrintNameOffset / sizeof(char)), 0,
268                    buffer.PrintNameLength / sizeof(char));
269                substituteName = new string(path + (buffer.SubstituteNameOffset / sizeof(char)), 0,
270                    buffer.SubstituteNameLength / sizeof(char));
271            }
272
273            return substituteName;
274        }
275
276        private static string ResolveReparsePointSymlink(IntPtr ptr, string path)
277        {
278            NativeMethods.REPARSE_DATA_BUFFER.SymbolicLinkReparseBuffer buffer =
279                (NativeMethods.REPARSE_DATA_BUFFER.SymbolicLinkReparseBuffer)
280                Marshal.PtrToStructure(ptr,
281                    typeof(NativeMethods.REPARSE_DATA_BUFFER.SymbolicLinkReparseBuffer));
282
283            //Get the substitute and print names from the buffer.
284            string substituteName;
285            string printName;
286            unsafe
287            {
288                char* pathBuffer = (char*)(((byte*)ptr.ToInt64()) + Marshal.SizeOf(buffer));
289                printName = new string(pathBuffer + (buffer.PrintNameOffset / sizeof(char)), 0,
290                    buffer.PrintNameLength / sizeof(char));
291                substituteName = new string(pathBuffer + (buffer.SubstituteNameOffset / sizeof(char)), 0,
292                    buffer.SubstituteNameLength / sizeof(char));
293            }
294
295            if ((buffer.Flags & NativeMethods.REPARSE_DATA_BUFFER.SymbolicLinkFlags.SYMLINK_FLAG_RELATIVE) != 0)
296            {
297                return Path.Combine(Path.GetDirectoryName(path), substituteName);
298            }
299
300            return substituteName;
301        }
302    }
303}
Note: See TracBrowser for help on using the repository browser.