// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Path.cs" company="Catel development team">
// Copyright (c) 2008 - 2011 Catel development team. All rights reserved.
// </copyright>
// <summary>
// Thrown when the requested directory cannot be created.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
using System;
using System.Reflection;
using System.Text;
using Catel.Properties;
using Catel.Reflection;
using log4net;
namespace Catel.IO
{
#region Exceptions
#endregion
/// <summary>
/// Static class that implements some path methods
/// </summary>
public static class Path
{
#region Constants
/// <summary>
/// Safe prefix for normal paths.
/// </summary>
private const string SafePrefixForNormalPaths = @"\\?\";
/// <summary>
/// Safe prefix for UNC paths.
/// </summary>
private const string SafePrefixForUncPaths = @"\\?\UNC\";
#endregion
#region Variables
/// <summary>
/// The <see cref="ILog">log</see> object.
/// </summary>
private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
#endregion
/// <summary>
/// Gets the application data directory for the company and product as defined the the assembly information of the entry assembly.
/// If the entry assembly is <c>null</c>, this method will fall back to the calling assembly to retrieve the information.
/// If the folder does not exist, the folder is automatically created by this method.
/// <para />
/// This method returns a value like [application data]\[company]\[product name].
/// </summary>
/// <returns>Directory for the application data.</returns>
public static string GetApplicationDataDirectory()
{
#if SILVERLIGHT
throw new NotSupportedInSilverlightException("Directories are protected");
#else
Assembly assembly = Assembly.GetEntryAssembly() ?? Assembly.GetCallingAssembly();
return GetApplicationDataDirectory(assembly.Company(), assembly.Product());
#endif
}
/// <summary>
/// Gets the application data directory for a specific product. If the folder does not exist, the folder is automatically created by this method.
/// <para />
/// This method returns a value like [application data]\[product name].
/// </summary>
/// <param name="productName">Name of the product.</param>
/// <returns>Directory for the application data.</returns>
public static string GetApplicationDataDirectory(string productName)
{
#if SILVERLIGHT
throw new NotSupportedInSilverlightException("Directories are protected");
#else
return GetApplicationDataDirectory(string.Empty, productName);
#endif
}
/// <summary>
/// Gets the application data directory for a specific product of a specific company. If the folder does not exist, the
/// folder is automatically created by this method.
/// <para />
/// This method returns a value like [application data]\[company]\[product name].
/// </summary>
/// <param name="companyName">Name of the company.</param>
/// <param name="productName">Name of the product.</param>
/// <returns>Directory for the application data.</returns>
/// <exception cref="CannotCreateDirectoryException">Thrown when the directory cannot be created.</exception>
public static string GetApplicationDataDirectory(string companyName, string productName)
{
#if SILVERLIGHT
throw new NotSupportedInSilverlightException("Directories are protected");
#else
string path = Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), companyName, productName);
if (!Directory.Exists(path))
{
Directory.CreateDirectory(path);
}
return path;
#endif
}
/// <summary>
/// Gets the name of the directory.
/// </summary>
/// <param name="path">The path to get the directory name from.</param>
/// <returns>The directory name.</returns>
/// <exception cref="ArgumentException">when <paramref name="path"/> is <c>null</c> or empty.</exception>
public static string GetDirectoryName(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "path");
}
return GetParentDirectory(path);
}
/// <summary>
/// Gets the name of the file.
/// </summary>
/// <param name="path">The path to get the file name from.</param>
/// <returns>The file name.</returns>
/// <exception cref="ArgumentException">when <paramref name="path"/> is <c>null</c> or empty.</exception>
public static string GetFileName(string path)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "path");
}
int lastSlashPosition = path.LastIndexOf(@"\");
if (lastSlashPosition == -1)
{
return path;
}
return path.Remove(0, lastSlashPosition + 1);
}
/// <summary>
/// Gets the long name of the path.
/// </summary>
/// <param name="shortPath">The short path.</param>
/// <returns>Long name of the path.</returns>
/// <exception cref="ArgumentException">when <paramref name="shortPath"/> is <c>null</c> or empty.</exception>
public static string GetLongPathName(string shortPath)
{
#if SILVERLIGHT
throw new NotSupportedInSilverlightException("Directories are protected");
#else
if (string.IsNullOrEmpty(shortPath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "shortPath");
}
StringBuilder builder = new StringBuilder(2048);
int result = Win32IOApi.GetLongPathName(shortPath, builder, builder.Capacity);
return (result > 0) ? builder.ToString(0, result) : shortPath;
#endif
}
/// <summary>
/// Gets the parent directory.
/// </summary>
/// <param name="path">The path to get the parent directory from.</param>
/// <returns>Parent directory of a path. If there is no parent directory, <see cref="string.Empty"/> is returned.</returns>
/// <remarks>
/// This method will always strip the trailing backslash from the parent.
/// </remarks>
public static string GetParentDirectory(string path)
{
string parent = string.Empty;
if (string.IsNullOrEmpty(path))
{
return parent;
}
path = RemoveTrailingSlashes(path);
if (!path.Contains(@"\"))
{
return parent;
}
int lastSlashPosition = path.LastIndexOf(@"\");
parent = path.Substring(0, lastSlashPosition);
parent = RemoveTrailingSlashes(parent);
return parent;
}
/// <summary>
/// Returns a relative path string from a full path.
/// <para />
/// The path to convert. Can be either a file or a directory
/// The base path to truncate to and replace
/// <para />
/// Lower case string of the relative path. If path is a directory it's returned
/// without a backslash at the end.
/// <para />
/// Examples of returned values:
/// .\test.txt, ..\test.txt, ..\..\..\test.txt, ., ..
/// </summary>
/// <param name="fullPath">Full path to convert to relative path.</param>
/// <returns>Relative path.</returns>
/// <remarks>
/// This method internally uses the <see cref="GetRelativePath(string,string)"/> method, but passes <c>null</c>
/// for the <c>basePath</c> parameter.
/// </remarks>
/// <exception cref="ArgumentException">when <paramref name="fullPath"/> is <c>null</c> or empty.</exception>
public static string GetRelativePath(string fullPath)
{
return GetRelativePath(fullPath, null);
}
/// <summary>
/// Returns a relative path string from a full path.
/// <para />
/// The path to convert. Can be either a file or a directory
/// The base path to truncate to and replace
/// <para />
/// Lower case string of the relative path. If path is a directory it's returned
/// without a backslash at the end.
/// <para />
/// Examples of returned values:
/// .\test.txt, ..\test.txt, ..\..\..\test.txt, ., ..
/// </summary>
/// <param name="fullPath">Full path to convert to relative path.</param>
/// <param name="basePath">The base path (a.k.a. working directory). If this parameter is <c>null</c> or empty, the current working directory will be used.</param>
/// <returns>Relative path.</returns>
/// <exception cref="ArgumentException">when <paramref name="fullPath"/> is <c>null</c> or empty.</exception>
public static string GetRelativePath(string fullPath, string basePath)
{
if (string.IsNullOrEmpty(fullPath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "fullPath");
}
if (string.IsNullOrEmpty(basePath))
{
basePath = Environment.CurrentDirectory;
}
fullPath = RemoveTrailingSlashes(fullPath.ToLower());
basePath = RemoveTrailingSlashes(basePath.ToLower());
// Check if the base path is really the full path (not just a subpath, for example "C:\MyTes" in "C:\MyTest")
string fullPathWithTrailingBackslash = AppendTrailingSlash(fullPath);
string basePathWithTrailingBackslash = AppendTrailingSlash(basePath);
if (fullPathWithTrailingBackslash.IndexOf(basePathWithTrailingBackslash) > -1)
{
string result = fullPath.Replace(basePath, string.Empty);
if (result.StartsWith("\\"))
{
result = result.Remove(0, 1);
}
return result;
}
string backDirs = string.Empty;
string partialPath = basePath;
int index = partialPath.LastIndexOf("\\");
while (index > 0)
{
partialPath = AppendTrailingSlash(partialPath.Substring(0, index));
backDirs = backDirs + "..\\";
if (fullPathWithTrailingBackslash.IndexOf(partialPath) > -1)
{
partialPath = RemoveTrailingSlashes(partialPath);
fullPath = RemoveTrailingSlashes(fullPath);
if (fullPath == partialPath)
{
// *** Full Directory match and need to replace it all
return fullPath.Replace(partialPath, backDirs.Substring(0, backDirs.Length - 1));
}
else
{
// *** We're dealing with a file or a start path
return fullPath.Replace(partialPath + (fullPath == partialPath ? string.Empty : "\\"), backDirs);
}
}
partialPath = RemoveTrailingSlashes(partialPath);
index = partialPath.LastIndexOf("\\", partialPath.Length - 1);
}
return fullPath;
}
/// <summary>
/// Returns the full path for a relative path.
/// </summary>
/// <param name="relativePath">Relative path to convert to a full path.</param>
/// <param name="basePath">Base path (a.k.a. working directory).</param>
/// <returns>Full path.</returns>
/// <exception cref="ArgumentException">when <paramref name="relativePath"/> is <c>null</c> or empty.</exception>
/// <exception cref="ArgumentException">when <paramref name="basePath"/> is <c>null</c> or empty.</exception>
public static string GetFullPath(string relativePath, string basePath)
{
if (string.IsNullOrEmpty(relativePath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "relativePath");
}
if (string.IsNullOrEmpty(basePath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "basePath");
}
string path = System.IO.Path.Combine(basePath, relativePath);
if (string.IsNullOrEmpty(path))
{
return string.Empty;
}
// Get the path info (it may contain any relative path items such as ..\, but
// now it is safe to call GetFullPath))
path = GetFullPath(path);
return path;
}
/// <summary>
/// Gets the full path.
/// </summary>
/// <param name="path">The relative path to get the full path of.</param>
/// <returns>The full path.</returns>
/// <exception cref="ArgumentException">when <paramref name="path"/> is <c>null</c> or empty.</exception>
public static string GetFullPath(string path)
{
#if SILVERLIGHT
return System.IO.Path.GetFullPath(path);
#else
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "path");
}
int bufferSize = 1;
path = AddWin32LongFileNamePrefix(path);
StringBuilder fullPath = new StringBuilder(bufferSize);
StringBuilder fileName = new StringBuilder(bufferSize);
uint finalPathSize = Win32IOApi.GetFullPathName(path, (uint)bufferSize, fullPath, fileName);
if (finalPathSize > bufferSize)
{
bufferSize = (int)finalPathSize + 10;
fullPath = new StringBuilder(bufferSize);
fileName = new StringBuilder(bufferSize);
finalPathSize = Win32IOApi.GetFullPathName(path, (uint)bufferSize, fullPath, fileName);
}
string finalPath = fullPath.ToString();
finalPath = RemoveWin32LongFileNamePrefix(finalPath);
return finalPath;
#endif
}
/// <summary>
/// Gets the name of the safe long file so paths longer than 255 are supported.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns>Safe filename (\\?\ prefixed).</returns>
/// <exception cref="ArgumentException">when <paramref name="filePath"/> is <c>null</c> or empty.</exception>
public static string GetWin32LongFileName(string filePath)
{
return AddWin32LongFileNamePrefix(filePath);
}
/// <summary>
/// Adds the \\?\ or \\?\UNC\ to the filename so Win32 files can safely contain more than 255 characters.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns>Win32 filename with long filename support.</returns>
/// <exception cref="ArgumentException">when <paramref name="filePath"/> is <c>null</c> or empty.</exception>
public static string AddWin32LongFileNamePrefix(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "filePath");
}
if (filePath.StartsWith("."))
{
return filePath;
}
if (filePath.StartsWith(SafePrefixForNormalPaths))
{
return filePath;
}
// If file path is disk file path then prepend it with \\?\
// if file path is UNC prepend it with \\?\UNC\ and remove \\ prefix in unc path.);
if (filePath.StartsWith(@"\\"))
{
filePath = SafePrefixForUncPaths + filePath.Substring(2, filePath.Length - 2);
}
else
{
filePath = SafePrefixForNormalPaths + filePath;
}
return filePath;
}
/// <summary>
/// Removes the \\?\ or \\?\UNC\ to the filename so Win32 files can safely contain more than 255 characters.
/// </summary>
/// <param name="filePath">The file path.</param>
/// <returns>Win32 filename with long filename support.</returns>
/// <exception cref="ArgumentException">when <paramref name="filePath"/> is <c>null</c> or empty.</exception>
public static string RemoveWin32LongFileNamePrefix(string filePath)
{
if (string.IsNullOrEmpty(filePath))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "filePath");
}
if (filePath.StartsWith(SafePrefixForUncPaths))
{
return filePath.Replace(SafePrefixForUncPaths, @"\\");
}
if (filePath.StartsWith(SafePrefixForNormalPaths))
{
return filePath.Replace(SafePrefixForNormalPaths, string.Empty);
}
return filePath;
}
/// <summary>
/// Appends a trailing backslash (\) to the path.
/// </summary>
/// <param name="path">Path to append the trailing backslash to.</param>
/// <returns>Path including the trailing backslash.</returns>
/// <exception cref="ArgumentException">when <paramref name="path"/> is <c>null</c> or empty.</exception>
public static string AppendTrailingSlash(string path)
{
return AppendTrailingSlash(path, System.IO.Path.DirectorySeparatorChar);
}
/// <summary>
/// Appends a trailing slash (\ or /) to the path.
/// </summary>
/// <param name="path">Path to append the trailing slash to.</param>
/// <param name="slash">Slash to append (\ or /).</param>
/// <returns>Path including the trailing slash.</returns>
/// <exception cref="ArgumentException">when <paramref name="path"/> is <c>null</c> or empty.</exception>
public static string AppendTrailingSlash(string path, char slash)
{
if (string.IsNullOrEmpty(path))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "path");
}
if (path[path.Length - 1] == slash)
{
return path;
}
return path + slash;
}
/// <summary>
/// Returns a combination of multiple paths.
/// </summary>
/// <param name="paths">Paths to combine.</param>
/// <returns>Combination of all the paths passed.</returns>
public static string Combine(params string[] paths)
{
string result = string.Empty;
// Make sure we have any values
if (paths.Length == 0)
{
return string.Empty;
}
if (paths.Length == 1)
{
return paths[0];
}
for (int i = 0; i < paths.Length; i++)
{
if (!string.IsNullOrEmpty(paths[i]))
{
result = System.IO.Path.Combine(result, paths[i]);
}
}
return result;
}
/// <summary>
/// Returns a combination of multiple urls.
/// </summary>
/// <param name="urls">Urls to combine.</param>
/// <returns>Combination of all the urls passed.</returns>
public static string CombineUrls(params string[] urls)
{
string result = string.Empty;
if (urls.Length == 0)
{
return string.Empty;
}
if (urls.Length == 1)
{
return ReplacePathSlashesByUrlSlashes(urls[0]);
}
// Store first path (remove trailing slashes only since we want to support root urls)
result = RemoveTrailingSlashes(urls[0]);
for (int i = 1; i < urls.Length; i++)
{
if (!string.IsNullOrEmpty(urls[i]))
{
result = RemoveTrailingSlashes(result);
string tempPath = RemoveStartAndTrailingSlashes(urls[i]);
result = Combine(result, tempPath);
}
}
return ReplacePathSlashesByUrlSlashes(result);
}
/// <summary>
/// Replaces path slashes (\) by url slashes (/).
/// </summary>
/// <param name="value">Value to convert.</param>
/// <returns>Prepared url.</returns>
/// <exception cref="ArgumentException">when <paramref name="value"/> is <c>null</c> or empty.</exception>
internal static string ReplacePathSlashesByUrlSlashes(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "value");
}
return value.Replace("\\", "/");
}
/// <summary>
/// Removes any slashes (\ or /) at the beginning of the string.
/// </summary>
/// <param name="value">Value to remove the slashes from.</param>
/// <returns>Value without slashes.</returns>
/// <exception cref="ArgumentException">when <paramref name="value"/> is <c>null</c> or empty.</exception>
internal static string RemoveStartSlashes(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "value");
}
while ((value.Length > 0) && ((value[0] == '\\') || (value[0] == '/')))
{
value = value.Remove(0, 1);
}
return value;
}
/// <summary>
/// Removes any slashes (\ or /) at the end of the string.
/// </summary>
/// <param name="value">Value to remove the slashes from.</param>
/// <returns>Value without slashes.</returns>
/// <exception cref="ArgumentException">when <paramref name="value"/> is <c>null</c> or empty.</exception>
internal static string RemoveTrailingSlashes(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "value");
}
while ((value.Length > 0) && ((value[value.Length - 1] == '\\') || (value[value.Length - 1] == '/')))
{
value = value.Remove(value.Length - 1, 1);
}
return value;
}
/// <summary>
/// Removes any slashes (\ or /) at the beginning and end of the string.
/// </summary>
/// <param name="value">Value to remove the slashes from.</param>
/// <returns>Value without trailing slashes.</returns>
/// <exception cref="ArgumentException">when <paramref name="value"/> is <c>null</c> or empty.</exception>
internal static string RemoveStartAndTrailingSlashes(string value)
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Exceptions.ArgumentCannotBeNullOrEmpty, "value");
}
value = RemoveStartSlashes(value);
value = RemoveTrailingSlashes(value);
return value;
}
}
}