//****************************************************************************************************** // FilePath.cs - Gbtc // // Copyright © 2012, Grid Protection Alliance. All Rights Reserved. // // Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See // the NOTICE file distributed with this work for additional information regarding copyright ownership. // The GPA licenses this file to you under the MIT License (MIT), the "License"; you may // not use this file except in compliance with the License. You may obtain a copy of the License at: // // http://www.opensource.org/licenses/MIT // // Unless agreed to in writing, the subject software distributed under the License is distributed on an // "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the // License for the specific language governing permissions and limitations. // // Code Modification History: // ---------------------------------------------------------------------------------------------------- // 02/05/2003 - J. Ritchie Carroll // Generated original version of source code. // 12/29/2005 - Pinal C. Patel // Migrated 2.0 version of source code from 1.1 source (GSF.Shared.FilePath). // 08/22/2007 - Darrell Zuercher // Edited code comments. // 09/19/2008 - J. Ritchie Carroll // Converted to C#. // 10/24/2008 - Pinal C. Patel // Edited code comments. // 12/17/2008 - F. Russell Robertson // Fixed issue in GetFilePatternRegularExpression(). // 06/30/2009 - Pinal C. Patel // Removed FilePathHasFileName() since the result was error prone. // 09/14/2009 - Stephen C. Wills // Added new header and license agreement. // 09/17/2009 - Pinal C. Patel // Modified GetAbsolutePath() to remove dependency on HttpContext.Current. // 04/19/2010 - Pinal C. Patel // Added GetApplicationDataFolder() method. // 04/21/2010 - Pinal C. Patel // Updated GetApplicationDataFolder() to include the company name if available. // 01/28/2011 - J. Ritchie Carroll // Added IsValidFileName function. // 02/14/2011 - J. Ritchie Carroll // Fixed issue in GetDirectoryName where last directory was being truncated as a file name. // 06/06/2011 - Stephen C. Wills // Fixed issue in GetFileName where path suffix was being removed before extracting the file name. // 07/29/2011 - Pinal C. Patel // Updated GetApplicationDataFolder() to use the TEMP directory for web applications. // 12/14/2012 - Starlynn Danyelle Gilliam // Modified Header. // //****************************************************************************************************** using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Web.Hosting; using GSF.Identity; using GSF.Interop; using GSF.Reflection; namespace GSF.IO { /// /// Contains File and Path manipulation methods. /// public static class FilePath { #region [ Members ] // Nested Types // ReSharper disable MemberCanBePrivate.Local [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct NETRESOURCE { public readonly int dwScope; public int dwType; public readonly int dwDisplayType; public readonly int dwUsage; public readonly string lpLocalName; public string lpRemoteName; public readonly string lpComment; public readonly string lpProvider; } // Constants private const int RESOURCETYPE_DISK = 0x1; // Fields private static readonly string s_directorySeparatorCharPattern; private static readonly string s_fileNameCharPattern; #endregion #region [ Constructor ] static FilePath() { char[] directorySeparatorChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; char[] encodedInvalidFileNameChars = Path.GetInvalidFileNameChars() .SelectMany(c => c.RegexEncode()) .ToArray(); char[] encodedDirectorySeparatorChars = directorySeparatorChars .Distinct() .SelectMany(c => c.RegexEncode()) .ToArray(); // Defines a regular expression pattern for a valid directory separator character. s_directorySeparatorCharPattern = $"[{new string(encodedDirectorySeparatorChars)}]"; // Defines a regular expression pattern for a valid file name character. We do this by // allowing any characters except those that would not be valid as part of a filename. // This essentially builds the "?" wildcard pattern match. s_fileNameCharPattern = $"[^{new string(encodedInvalidFileNameChars)}]"; } #endregion #region [ Methods ] /// /// Connects to a network share with the specified user's credentials. /// /// UNC share name to connect to. /// User name to use for connection. /// Password to use for connection. /// Domain name to use for connection. Specify the computer name for local system accounts. public static void ConnectToNetworkShare(string sharename, string userName, string password, string domain) { if (Common.IsPosixEnvironment) throw new NotImplementedException("Failed to connect to network share \"" + sharename + "\" as user " + userName + " - not implemented in POSIX environment"); NETRESOURCE resource = new NETRESOURCE(); int result; resource.dwType = RESOURCETYPE_DISK; resource.lpRemoteName = sharename; if (domain.Length > 0) userName = domain + "\\" + userName; result = WNetAddConnection2(ref resource, password, userName, 0); if (result != 0) throw new InvalidOperationException("Failed to connect to network share \"" + sharename + "\" as user " + userName + " - " + WindowsApi.GetErrorMessage(result)); } /// /// Disconnects the specified network share. /// /// UNC share name to disconnect from. public static void DisconnectFromNetworkShare(string sharename) { DisconnectFromNetworkShare(sharename, true); } /// /// Disconnects the specified network share. /// /// UNC share name to disconnect from. /// true to force a disconnect; otherwise false. public static void DisconnectFromNetworkShare(string sharename, bool force) { if (Common.IsPosixEnvironment) throw new NotImplementedException("Failed to disconnect from network share \"" + sharename + "\" - not implemented in POSIX environment"); int result = WNetCancelConnection2(sharename, 0, force); if (result != 0) throw new InvalidOperationException("Failed to disconnect from network share \"" + sharename + "\" - " + WindowsApi.GetErrorMessage(result)); } /// /// Determines whether the specified file name matches any of the given file specs (wildcards are defined as '*' or '?' characters). /// /// The file specs used for matching the specified file name. /// The file path to be tested against the specified file specs for a match. /// true to specify a case-insensitive match; otherwise false. /// true if the specified file name matches any of the given file specs; otherwise false. /// contains one or more of the invalid characters defined in . /// /// The syntax for adheres to the following rules: /// /// /// public static bool IsFilePatternMatch(string[] fileSpecs, string filePath, bool ignoreCase) { foreach (string fileSpec in fileSpecs) { if (IsFilePatternMatch(fileSpec, filePath, ignoreCase)) return true; } return false; } /// /// Determines whether the specified file name matches the given file spec (wildcards are defined as '*' or '?' characters). /// /// The file spec used for matching the specified file name. /// The file path to be tested against the specified file spec for a match. /// true to specify a case-insensitive match; otherwise false. /// true if the specified file name matches the given file spec; otherwise false. /// contains one or more of the invalid characters defined in . /// /// The syntax for adheres to the following rules: /// /// /// public static bool IsFilePatternMatch(string fileSpec, string filePath, bool ignoreCase) { // Define regular expression patterns for the three possible sequences that can match the path root. string recursiveDirPattern = $"{(Regex.Escape("**"))}{s_directorySeparatorCharPattern}"; string pathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.DirectorySeparatorChar.ToString())); string altPathRootPattern = string.Format("({0}[^{0}]|{0}$)", Regex.Escape(Path.AltDirectorySeparatorChar.ToString())); // If any of the three patterns are found at the start of fileSpec and the fileName refers to a rooted path, // remove the path root from fileName and then remove all leading directory separator chars from both fileSpec and fileName. if (Regex.IsMatch(fileSpec, $"^({recursiveDirPattern}|{pathRootPattern}|{altPathRootPattern})") && Path.IsPathRooted(filePath)) { fileSpec = fileSpec.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); filePath = Regex.Replace(filePath, $"^{Regex.Escape(Path.GetPathRoot(filePath))}", "").TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); } // Use regular expression matching to determine whether fileSpec matches fileName. return (new Regex(GetFilePatternRegularExpression(fileSpec), (ignoreCase ? RegexOptions.IgnoreCase : RegexOptions.None))).IsMatch(filePath); } /// /// Determines if the specified file name and path is valid. /// /// The file name with optional path to test for validity. /// true if the specified is a valid name; otherwise false. public static bool IsValidFileName(string filePath) { if (string.IsNullOrWhiteSpace(filePath)) return false; try { // Attempt to parse directory and file name - this will catch most issues string directory = Path.GetDirectoryName(filePath); string filename = Path.GetFileName(filePath); // Check for invalid directory characters if (!string.IsNullOrWhiteSpace(directory) && directory.IndexOfAny(Path.GetInvalidPathChars()) >= 0) return false; // Check for invalid file name characters if (string.IsNullOrWhiteSpace(filename) || filename.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) return false; // Recurse in to check validity of each directory name if (!string.IsNullOrWhiteSpace(directory) && !string.IsNullOrWhiteSpace(Path.GetFileName(directory))) return IsValidFileName(directory); } catch { // If you can't parse a file name or directory, file path is definitely not valid return false; } return true; } /// /// Gets a valid file name by replacing invalid file name characters with . /// /// File name to validate. /// Character to replace invalid file name characters with. Set to '\0' to remove invalid file name characters. /// A valid file name with no invalid file name characters. /// /// This function expects a file name, not a file name with a path. To properly get a valid file path, where all directory /// names and the file name are validated, use the . Calling the /// function will a full path will yield all directory separators replaced with the . /// public static string GetValidFileName(string fileName, char replaceWithCharacter = '_') { if (replaceWithCharacter == '\0') return fileName.RemoveInvalidFileNameCharacters(); return fileName.ReplaceInvalidFileNameCharacters(replaceWithCharacter); } /// /// Gets a valid file path by replacing invalid file or directory name characters with . /// /// File path to validate. /// Character to replace invalid file or directory name characters with. Set to '\0' to remove invalid file or directory name characters. /// A valid file path with no invalid file or directory name characters. public static string GetValidFilePath(string filePath, char replaceWithCharacter = '_') { string[] fileParts = filePath.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); for (int i = 0; i < fileParts.Length; i++) { // Leave any volume specification alone if (i == 0 && Path.VolumeSeparatorChar != Path.DirectorySeparatorChar && Path.VolumeSeparatorChar != Path.AltDirectorySeparatorChar && fileParts[0].IndexOfAny(new[] { Path.VolumeSeparatorChar }) > 0) continue; fileParts[i] = GetValidFileName(fileParts[i], replaceWithCharacter); } return string.Join(Path.DirectorySeparatorChar.ToString(), fileParts); } /// /// Gets the file name and extension from the specified file path. /// /// The file path from which the file name and extension is to be obtained. /// File name and extension if the file path has it; otherwise empty string. public static string GetFileName(string filePath) { return Path.GetFileName(filePath); } /// /// Gets the extension from the specified file path. /// /// The file path from which the extension is to be obtained. /// File extension. public static string GetExtension(string filePath) { return Path.GetExtension(RemovePathSuffix(filePath)); } /// /// Gets the file name without extension from the specified file path. /// /// The file path from which the file name is to be obtained. /// File name without the extension if the file path has it; otherwise empty string. public static string GetFileNameWithoutExtension(string filePath) { return Path.GetFileNameWithoutExtension(RemovePathSuffix(filePath)); } /// /// Gets the size of the specified file. /// /// Name of file whose size is to be retrieved. /// The size of the specified file. public static long GetFileLength(string fileName) { try { return (new FileInfo(fileName)).Length; } catch { return -1; } } /// /// Gets a list of files under the specified path. Search wildcard pattern (c:\Data\*.dat) can be used for /// including only the files matching the pattern or path wildcard pattern (c:\Data\*\*.dat) to indicate the /// inclusion of files under all subdirectories in the list. /// /// The path for which a list of files is to be returned. /// A list of files under the given path. public static string[] GetFileList(string path) { string directory = GetDirectoryName(path); string filePattern = GetFileName(path); SearchOption options = SearchOption.TopDirectoryOnly; if (string.IsNullOrEmpty(filePattern)) { // No wildcard pattern was specified, so get a listing of all files. filePattern = "*.*"; } if (GetLastDirectoryName(directory) == "*") { // Path wildcard pattern is used to specify the option to include subdirectories. options = SearchOption.AllDirectories; directory = directory.Remove(directory.LastIndexOf("*", StringComparison.OrdinalIgnoreCase)); } return Directory.GetFiles(directory, filePattern, options); } /// /// Gets a regular expression pattern that simulates wildcard matching for filenames (wildcards are defined as '*' or '?' characters). /// /// The file spec for which the regular expression pattern if to be generated. /// Regular expression pattern that simulates wildcard matching for filenames. public static string GetFilePatternRegularExpression(string fileSpec) { List> replacements = new List>() { // Replaces directory separator characters with their equivalent regular expressions. Tuple.Create($"{s_directorySeparatorCharPattern}+", $"{s_directorySeparatorCharPattern}+"), // Replaces wildcard file patterns with their equivalent regular expression. Tuple.Create(Regex.Escape("?"), s_fileNameCharPattern), Tuple.Create(Regex.Escape("**") + s_directorySeparatorCharPattern, $"({s_fileNameCharPattern}*{s_directorySeparatorCharPattern})*"), Tuple.Create(Regex.Escape("*"), $"{s_fileNameCharPattern}*") }; StringBuilder input; StringBuilder output; Tuple replacement; Match match; input = new StringBuilder(fileSpec); output = new StringBuilder(); while (input.Length > 0) { // Determine if any of the replacement patterns match the input. replacement = replacements.FirstOrDefault(r => Regex.IsMatch(input.ToString(), $"^{r.Item1}")); if ((object)replacement != null) { // If one of the replacement patterns matches the input, // apply that replacement and write it to the output. match = Regex.Match(input.ToString(), $"^{replacement.Item1}"); output.Append(Regex.Replace(match.Value, replacement.Item1, replacement.Item2)); input.Remove(0, match.Length); } else { // If not, move a single character as-is from input to the output. output.Append(Regex.Escape(input[0].ToString())); input.Remove(0, 1); } } // Return the output string as the regular expression pattern. return "^" + output.ToString() + "$"; } /// /// Gets the directory information from the specified file path. /// /// The file path from which the directory information is to be obtained. /// Directory information. /// /// This differs from in that it will see if last name in path is /// a directory and, if it exists, will properly treat that part of path as a directory. The .NET path /// function always assumes last entry is a file name if path is not suffixed with a slash. For example: /// Path.GetDirectoryName(@"C:\Music") will return "C:\", however, /// FilePath.GetDirectoryName(@"C:\Music") will return "C:\Music\", so long as Music directory exists. /// public static string GetDirectoryName(string filePath) { // Test for case where valid path does not end in directory separator, Path.GetDirectoryName assumes // this is a file name - whether is exists or not :-( string directoryName = AddPathSuffix(filePath); if (Directory.Exists(directoryName)) return directoryName; return AddPathSuffix(Path.GetDirectoryName(filePath) ?? filePath); } /// /// Gets the last directory name from a file path. /// /// The file path from where the last directory name is to be retrieved. /// The last directory name from a file path. /// /// would return sub2 from c:\windows\sub2\filename.ext. /// public static string GetLastDirectoryName(string filePath) { // Test case should verify the following: // FilePath.GetLastDirectoryName(@"C:\Test\sub") == "Test" <-- sub assumed to be filename // FilePath.GetLastDirectoryName(@"C:\Test\sub\") == "sub" <-- sub assumed to be directory if (!string.IsNullOrEmpty(filePath)) { int index; char[] dirVolChars = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar, Path.VolumeSeparatorChar }; // Remove file name and trailing directory separator character from the file path. filePath = RemovePathSuffix(GetDirectoryName(filePath)); // Keep going through the file path until all directory separator characters are removed. while ((index = filePath.IndexOfAny(dirVolChars)) > -1) filePath = filePath.Substring(index + 1); return filePath; } throw new ArgumentNullException("filePath"); } /// /// Gets the absolute file path for the specified file name or relative file path. /// /// File name or relative file path. /// Absolute file path for the specified file name or relative file path. public static string GetAbsolutePath(string filePath) { if (!Path.IsPathRooted(filePath)) { // The specified path is a relative one since it is not rooted. switch (Common.GetApplicationType()) { // Prepends the application's root to the file path. case ApplicationType.Web: filePath = Path.Combine(HostingEnvironment.ApplicationPhysicalPath, filePath); break; default: try { filePath = Path.Combine(GetDirectoryName(AssemblyInfo.EntryAssembly.Location), filePath); } catch { // Fall back on executing assembly path if entry assembly is not available filePath = Path.Combine(GetDirectoryName(AssemblyInfo.ExecutingAssembly.Location), filePath); } break; } } return RemovePathSuffix(filePath); } /// /// Determines if the specified is contained with the current executable path. /// /// File name or relative file path. /// true if the specified is contained with the current executable path; otherwise false. public static bool InApplicationPath(string filePath) { return GetAbsolutePath(filePath).StartsWith(GetAbsolutePath(""), StringComparison.OrdinalIgnoreCase); } /// /// Gets the path to the folder where data related to the current application can be stored. /// /// Path to the folder where data related to the current application can be stored. public static string GetApplicationDataFolder() { string rootFolder; switch (Common.GetApplicationType()) { case ApplicationType.Web: // Treat web application special. if (HostingEnvironment.ApplicationVirtualPath == "/") rootFolder = Path.Combine(Path.GetTempPath(), HostingEnvironment.SiteName); else rootFolder = Path.Combine(Path.GetTempPath(), HostingEnvironment.ApplicationVirtualPath.ToNonNullString().Trim('/')); // Create a user folder if ID is available. string userID = UserInfo.RemoteUserID; // ID is not available. if (string.IsNullOrEmpty(userID)) return rootFolder; // Remove domain from ID. if (!userID.Contains("\\")) return Path.Combine(rootFolder, userID); return Path.Combine(rootFolder, userID.Remove(0, userID.IndexOf('\\') + 1)); default: rootFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); if (string.IsNullOrEmpty(AssemblyInfo.EntryAssembly.Company)) return Path.Combine(rootFolder, AssemblyInfo.EntryAssembly.Name); return Path.Combine(rootFolder, AssemblyInfo.EntryAssembly.Company, AssemblyInfo.EntryAssembly.Name); } } /// /// Gets the path to the folder where data related to the current /// application can be stored as well as shared among different users. /// /// Path to the folder where data related to the current application can be stored. public static string GetCommonApplicationDataFolder() { string rootFolder = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); if (string.IsNullOrEmpty(AssemblyInfo.EntryAssembly.Company)) return Path.Combine(rootFolder, AssemblyInfo.EntryAssembly.Name); return Path.Combine(rootFolder, AssemblyInfo.EntryAssembly.Company, AssemblyInfo.EntryAssembly.Name); } /// /// Makes sure path is suffixed with standard . /// /// The file path to be suffixed. /// Suffixed path. public static string AddPathSuffix(string filePath) { if (string.IsNullOrEmpty(filePath)) { filePath = Path.DirectorySeparatorChar.ToString(); } else { char suffixChar = filePath[filePath.Length - 1]; if (suffixChar != Path.DirectorySeparatorChar && suffixChar != Path.AltDirectorySeparatorChar) filePath += Path.DirectorySeparatorChar; } return filePath; } /// /// Makes sure path is not suffixed with or . /// /// The file path to be unsuffixed. /// Unsuffixed path. public static string RemovePathSuffix(string filePath) { if (string.IsNullOrEmpty(filePath)) { filePath = ""; } else { char suffixChar = filePath[filePath.Length - 1]; while ((suffixChar == Path.DirectorySeparatorChar || suffixChar == Path.AltDirectorySeparatorChar) && filePath.Length > 0) { filePath = filePath.Substring(0, filePath.Length - 1); if (filePath.Length > 0) suffixChar = filePath[filePath.Length - 1]; } } return filePath; } /// /// Remove any path root present in the path. /// /// The file path whose root is to be removed. /// The path with the root removed if it was present. public static string DropPathRoot(string filePath) { // JRC: Changed this to the following more simple algorithm if (string.IsNullOrEmpty(filePath)) return ""; return filePath.Remove(0, Path.GetPathRoot(filePath).Length); #region [ Original Code ] //string result = filePath; //if (!string.IsNullOrEmpty(filePath)) //{ // if ((filePath[0] == '\\') || (filePath[0] == '/')) // { // // UNC name ? // if ((filePath.Length > 1) && ((filePath[1] == '\\') || (filePath[1] == '/'))) // { // int index = 2; // int elements = 2; // // Scan for two separate elements \\machine\share\restofpath // while ((index <= filePath.Length) && // (((filePath[index] != '\\') && (filePath[index] != '/')) || (--elements > 0))) // { // index++; // } // index++; // if (index < filePath.Length) // { // result = filePath.Substring(index); // } // else // { // result = ""; // } // } // } // else if ((filePath.Length > 1) && (filePath[1] == ':')) // { // int dropCount = 2; // if ((filePath.Length > 2) && ((filePath[2] == '\\') || (filePath[2] == '/'))) // { // dropCount = 3; // } // result = result.Remove(0, dropCount); // } //} //return result; #endregion } /// /// Returns a file name, for display purposes, of the specified length using "..." to indicate a longer name. /// /// The file path to be trimmed. /// The maximum length of the trimmed file path. /// Trimmed file path. /// /// Minimum value for the parameter is 12. 12 will be used for any value /// specified as less than 12. /// public static string TrimFileName(string filePath, int length) { if (string.IsNullOrEmpty(filePath)) filePath = ""; else filePath = filePath.Trim(); if (length < 12) length = 12; if (filePath.Length > length) { string justName = GetFileName(filePath); if (justName.Length == filePath.Length) { // This is just a file name. Make sure extension shows. string justExtension = GetExtension(filePath); string trimName = GetFileNameWithoutExtension(filePath); if (trimName.Length > 8) { if (justExtension.Length > length - 8) justExtension = justExtension.Substring(0, length - 8); double offsetLen = (length - justExtension.Length - 3) / 2.0D; return trimName.Substring(0, (int)(Math.Ceiling(offsetLen))) + "..." + trimName.Substring((int)Math.Round(trimName.Length - Math.Floor(offsetLen) + 1.0D)) + justExtension; } // We can not trim file names less than 8 with a "...", so we truncate long extension. return trimName + justExtension.Substring(0, length - trimName.Length); } // File name alone exceeds length - recurses into function without path. if (justName.Length > length) return TrimFileName(justName, length); // File name contains path. Trims path before file name. string justFilePath = GetDirectoryName(filePath); int offset = length - justName.Length - 4; if (justFilePath.Length > offset && offset > 0) return justFilePath.Substring(0, offset) + "..." + Path.DirectorySeparatorChar + justName; // Can not fit path. Trims file name. return TrimFileName(justName, length); } // Full file name fits within requested length. return filePath; } /// /// Gets a unique file path for the given file by checking for name collisions and /// adding a sequence number to the end of the file name if there is a collision. /// /// The path to the original file before adding the sequence number. /// The unique path to the file. /// /// This method is designed to handle the case where the user wishes to create a file in a folder /// with a given name when there is a possibility that the name is already taken. Using this method, /// it is possible to create files with names in the following format: /// ///
    ///
  • File.ext
  • ///
  • File (1).ext
  • ///
  • File (2).ext
  • ///
  • ...
  • ///
/// /// This method uses a linear search to find a unique file name, so it is suitable for situations where /// there are a small number of collisions for each file name. This will detect and fill gaps that can /// occur when files are deleted (for instance, if "File (1).ext" were deleted from the list above). ///
public static string GetUniqueFilePath(string originalFilePath) { string uniqueFilePath = GetAbsolutePath(originalFilePath); string directory = GetDirectoryName(uniqueFilePath); string originalFileRoot = GetFileNameWithoutExtension(uniqueFilePath); string fileExtension = GetExtension(uniqueFilePath); int i = 1; while (File.Exists(uniqueFilePath)) { uniqueFilePath = Path.Combine(directory, string.Format("{0} ({1}){2}", originalFileRoot, i, fileExtension)); i++; } return uniqueFilePath; } /// /// Gets a unique file path for the given file by checking for name collisions and /// adding a sequence number to the end of the file name if there is a collision. /// /// The path to the original file before adding the sequence number. /// The unique path to the file. /// /// This method is designed to handle the case where the user wishes to create a file in a folder /// with a given name when there is a possibility that the name is already taken. Using this method, /// it is possible to create files with names in the following format: /// ///
    ///
  • File.ext
  • ///
  • File (1).ext
  • ///
  • File (2).ext
  • ///
  • ...
  • ///
/// /// This method uses a binary search to find a unique file name, so it is suitable for situations where /// a large number of files will be created with the same file name, and the next unique file name needs /// to be found relatively quickly. It will not always detect gaps in the sequence numbers that can occur /// when files are deleted (for instance, if "File (1).ext" were deleted from the list above). ///
public static string GetUniqueFilePathWithBinarySearch(string originalFilePath) { string uniqueFilePath = GetAbsolutePath(originalFilePath); string directory = GetDirectoryName(uniqueFilePath); string originalFileRoot = GetFileNameWithoutExtension(uniqueFilePath); string fileExtension = GetExtension(uniqueFilePath); int i = 1; int j = 1; int k = 1; if (!File.Exists(uniqueFilePath)) return uniqueFilePath; while (File.Exists(uniqueFilePath)) { uniqueFilePath = Path.Combine(directory, string.Format("{0} ({1}){2}", originalFileRoot, i, fileExtension)); j = k; k = i; i *= 2; } while (j < k) { i = (j + k) / 2; uniqueFilePath = Path.Combine(directory, string.Format("{0} ({1}){2}", originalFileRoot, i, fileExtension)); if (File.Exists(uniqueFilePath)) j = i + 1; else k = i; } return Path.Combine(directory, string.Format("{0} ({1}){2}", originalFileRoot, k, fileExtension)); } /// /// Attempts to get read access on a file. /// /// The file to check for read access. /// True if read access is obtained; false otherwise. public static bool TryGetReadLock(string fileName) { try { using (File.OpenRead(fileName)) return true; } catch { return false; } } /// /// Attempts to get read access on a file. /// /// The file to check for read access. /// True if read access is obtained; false otherwise. public static bool TryGetReadLockExclusive(string fileName) { try { using (new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.None)) return true; } catch { return false; } } /// /// Attempts to get write access on a file. /// /// The file to check for write access. /// True if write access is obtained; false otherwise. public static bool TryGetWriteLock(string fileName) { try { using (File.OpenWrite(fileName)) return true; } catch { return false; } } /// /// Waits for the default duration (5 seconds) for read access on a file. /// /// The name of the file to wait for to obtain read access. public static void WaitForReadLock(string fileName) { WaitForReadLock(fileName, 5); } /// /// Waits for read access on a file for the specified number of seconds. /// /// The name of the file to wait for to obtain read access. /// The time to wait for in seconds to obtain read access on a file. Set to zero to wait infinitely. public static void WaitForReadLock(string fileName, double secondsToWait) { if (!File.Exists(fileName)) throw new FileNotFoundException("Could not test file lock for \"" + fileName + "\", file does not exist", fileName); // Keeps trying for a file lock. double startTime = Common.SystemTimer; while (!TryGetReadLock(fileName)) { if (secondsToWait > 0) { if (Common.SystemTimer > startTime + secondsToWait) throw new IOException("Could not open \"" + fileName + "\" for read access, tried for " + secondsToWait + " seconds"); } // Yields to all other system threads. Thread.Sleep(250); } } /// /// Waits for the default duration (5 seconds) for read access on a file. /// /// The name of the file to wait for to obtain read access. public static void WaitForReadLockExclusive(string fileName) { WaitForReadLockExclusive(fileName, 5); } /// /// Waits for read access on a file for the specified number of seconds. /// /// The name of the file to wait for to obtain read access. /// The time to wait for in seconds to obtain read access on a file. Set to zero to wait infinitely. public static void WaitForReadLockExclusive(string fileName, double secondsToWait) { if (!File.Exists(fileName)) throw new FileNotFoundException("Could not test file lock for \"" + fileName + "\", file does not exist", fileName); // Keeps trying for a file lock. double startTime = Common.SystemTimer; while (!TryGetReadLockExclusive(fileName)) { if (secondsToWait > 0) { if (Common.SystemTimer > startTime + secondsToWait) throw new IOException("Could not open \"" + fileName + "\" for read access, tried for " + secondsToWait + " seconds"); } // Yields to all other system threads. Thread.Sleep(250); } } /// /// Waits for the default duration (5 seconds) for write access on a file. /// /// The name of the file to wait for to obtain write access. public static void WaitForWriteLock(string fileName) { WaitForWriteLock(fileName, 5); } /// /// Waits for write access on a file for the specified number of seconds. /// /// The name of the file to wait for to obtain write access. /// The time to wait for in seconds to obtain write access on a file. Set to zero to wait infinitely. public static void WaitForWriteLock(string fileName, double secondsToWait) { if (!File.Exists(fileName)) throw new FileNotFoundException("Could not test file lock for \"" + fileName + "\", file does not exist", fileName); // Keeps trying for a file lock. double startTime = Common.SystemTimer; while (!TryGetWriteLock(fileName)) { if (secondsToWait > 0) { if (Common.SystemTimer > startTime + secondsToWait) throw new IOException("Could not open \"" + fileName + "\" for write access, tried for " + secondsToWait + " seconds"); } // Yields to all other system threads. Thread.Sleep(250); } } /// /// Waits for the default duration (5 seconds) for a file to exist. /// /// The name of the file to wait for until it is created. public static void WaitTillExists(string fileName) { WaitTillExists(fileName, 5); } /// /// Waits for a file to exist for the specified number of seconds. /// /// The name of the file to wait for until it is created. /// The time to wait for in seconds for the file to be created. Set to zero to wait infinitely. public static void WaitTillExists(string fileName, double secondsToWait) { // Keeps waiting for a file to be created. double startTime = Common.SystemTimer; while (!File.Exists(fileName)) { if (secondsToWait > 0) { if (Common.SystemTimer > startTime + secondsToWait) throw new IOException("Waited for \"" + fileName + "\" to exist for " + secondsToWait + " seconds, but it was never created"); } // Yields to all other system threads. Thread.Sleep(250); } } [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2W", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] private static extern int WNetAddConnection2(ref NETRESOURCE lpNetResource, string lpPassword, string lpUsername, int dwFlags); [DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2W", ExactSpelling = true, CharSet = CharSet.Unicode, SetLastError = true)] private static extern int WNetCancelConnection2(string lpName, int dwFlags, [MarshalAs(UnmanagedType.Bool)] bool fForce); #endregion } }