//******************************************************************************************************
// 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:
///
///
/// - Either '\' or '/' (as defined by and ) can match the other.
/// - A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").
/// - A '?' matches a single character which would be valid in a file name (as defined by ).
/// - A '*' matches any number of characters which would be valid in a file name.
/// - A sequence of "**\" or "**/" matches any number of sequential directories.
/// - Any other character matches itself.
///
///
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:
///
///
/// - Either '\' or '/' (as defined by and ) can match the other.
/// - A single '\' or '/' at the beginning of the pattern matches any valid path root (such as "C:\" or "\\server\share").
/// - A '?' matches a single character which would be valid in a file name (as defined by ).
/// - A '*' matches any number of characters which would be valid in a file name.
/// - A sequence of "**\" or "**/" matches any number of sequential directories.
/// - Any other character matches itself.
///
///
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
}
}